PHP Tricks

These entries highlight PHP’s lesser-known corners. Lots of language abuse, and the occasional gem.

Due to working with Drupal for most of this year, I’ve seen this construct so many times it’s killed me a little inside:

switch($a) {
    case TRUE:
        switch($b) {
            case TRUE:
                break;
            case FALSE:
                break;
        }
        break;
    case FALSE:
        switch($b) {
            case TRUE:
                break;
            case FALSE:
                break;
        }
        break;
}

Someone I work with, who wishes to keep his name off the net, came up with this:

switch(array($a, $b)) {
    case array(false, false):
        break;
    case array(false, true):
        break;
    case array(true, false):
        break;
    case array(true, true):
        break;
}

I’d rather avoid the need for nested switches altogether, but this construct makes it easy to collapse similar branches, and is, I think, a useful addition to my toolkit:

switch(array($a,$b)) {
    case array(true, true):
        break;
    case array(false, false):
    case array(false, true):
    case array(true, false):
    default:
        break;
}

It’s worth mentioning that arrays are compared for equality (==) rather than identicality (===).

A fragment of PHP that really shouldn’t work. I hope nobody ever finds a use for this:

switch($n)
{
    case 1;
        echo 'one';
        break;
    case 2;
        echo 'two';
        break;
    case 3;
        echo 'three';
        break;
}

Check this out:

if (isset($a, $b, $c))
{
    unset($a, $b, $c);
}

Mulitple arguments. Looks like they’ve been there since an early PHP4 release, at least. Why does nobody tell me these things?

A base class is "a class from which other classes are derived".

Many OO languages have the concept of a single base class from which all other classes are explicitly or implicitly descended. For example, Ruby, Java and .NET all have Object.

It’s a very common belief that PHP implements stdClass as a base class for all objects, but this is in fact not the case:

<?php
 
class DoesNotExtend {}
 
class DoesExtend extends stdClass {}
 
$doesNotExtend = new DoesNotExtend();
$doesExtend = new DoesExtend();
 
var_dump($doesNotExtend instanceof stdClass);
var_dump($doesExtend instanceof stdClass);

Outputs:

bool(false)
bool(true)

When a language is defined by its implementation rather than a standard, it can sometimes be tricky to decide what should be considered correct behaviour and what should be considered an implementation bug.

What follows isn’t so much a PHP trick as a fix for something that really should work, but doesn’t. Although the manual implies that the behaviour described below is specific to Zend Engine 1, all my tests were performed against Zend Engine 2.2, PHP 5.2.5.

Quoting from the manual:

The Zend Engine 1, driving PHP 4, implements the static and global modifier for variables in terms of references. For example, a true global variable imported inside a function scope with the global statement actually creates a reference to the global variable. This can lead to unexpected behaviour which the following example addresses:

<?php
function test_global_ref() {
    global $obj;
    $obj = &new stdclass;
}
 
function test_global_noref() {
    global $obj;
    $obj = new stdclass;
}
 
test_global_ref();
var_dump($obj);
test_global_noref();
var_dump($obj);
?>

Executing this example will result in the following output:

NULL
object(stdClass)(0) {}

http://uk2.php.net/static

The example above uses instances of stdClass, but attempts to assign references to scalar values, arrays or resources to global variables have the same result: the attempt to modify the global fails without error (even with error_reporting(E_ALL)), and the global retains whatever value it had before the function call.

The workaround is very simple – assign the reference via the $GLOBALS superglobal:

<?php
 
function test_global_ref()
{
    $GLOBALS['obj'] = &new stdclass;
}
 
test_global_ref();
var_dump($obj);

Outputs:

object(stdClass)#1 (0) { }

I think one of these scripts must expose an implementation bug – either assigning a reference to a global variable should work, in which case the first script should not fail, or it should not work and the second script should fail. It would be interesting to get an opinion from someone involved in language internals on how PHP should behave. In either case, it’s very hard to understand why the first script doesn’t currently cause a notice or error to be thrown when the assignment fails.

From the PHP manual:

A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. As a regular expression, it would be expressed thus: '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'

In other words, variable names can’t begin with a numeral. However there are a couple of ways to slip an illegal variable name into the symbol table:

$var = '1';
 
$$var = 'hello world';

or

${'1'} = 'hello world';

You can prove to your own satisfaction that the variables really exist with PHP’s rather obscure compact() function:

print_r( compact( '1' ));

Please note: if you use any of this in production code – and that includes compact() – I will come to your house and beat you with something heavy.

Array notation is fine, but it can look a bit clunky when you’re working with complex structures. This is a fairly simple example, but I’m sure we’ve all dealt with worse:

 
$clientChanges['deletes'][$val['fkClient']] = $val['Total'];

Casting the array to an object allows us to use object notation (->) and makes the code more readable:

 
$val = (object) $val;
$clientChanges = (object) $clientChanges;
 
$clientChanges->deletes[$val->fkClient] = $val->Total;

You can even get away with using array functions on objects, as long as they’re just simple collections of properties:

 
$o3 = (object) array_merge( (array) $o1, (array) $o2 );

Of course, member functions won’t make it through this kind of mangling, but bizarrely, private variables do:

 
class O
{
    private $a = 4;
    var $b = 5;
    var $c = 6;
}
 
$o = new O();
 
$o = ( object ) array_reverse( ( array )$o );
 
var_dump( $o );
 
/**
 * outputs:
 *
 * object(stdClass)#2 (3) {
 *     ["c"]=> int(6)
 *     ["b"]=> int(5)
 *     ["a:private"]=> int(4)
 * }
 */

PHP is a weakly-typed language. By that I mean that variables are assigned values without regard to variable type, and implicit conversion at runtime sorts out any conflicts. PHP will happily let you compare a string and an integer, and if the string contains something unexpected, well, you should have paid more attention to data validation.

Given that the most important feature of PHP is the shallow learning curve, and that it is deployed in an environment where everything is a string, it’s (just) possible to argue that this was a good design decision. It allows you to do things like this:

 
$n = "5";
echo 3 + $n;

without getting into lots of tiresome explicit conversion. But the implicit rules are sometimes a little too clever for their own good, and throw up oddities like this:

 
$a = 'string';
$b = 0;
 
if ( $a == true && $b == false && $a == $b )
{
    echo ( 'universe broken' );
}

What’s going on here? Lets look at these three clauses in detail:

  • ( 'string' == true ) because any non-null string evaluates to true when compared with a boolean
  • ( 0 == false ) because the integer 0 undergoes implicit conversion to boolean and evaluates to false
  • Finally, ( 'string' == 0 ) because a string is silently promoted to integer when compared with an integer. If the string is the ASCII representation of a number (eg "123"), it is assigned that value. If it doesn’t contain a number, it is assigned the value 0. So our third clause evaluates as true. Oops…

I routinely use the triple-equals operator (identical) instead of the double-equals operator (equality) now, but it feels and looks like a hack to avoid someone else’s bad design decision biting me. PHP is chock-full of these little oddities.

If you want all the gory details, the type comparison tables are tucked deep inside the PHP Manual.

Update: This post over at ycombinator contains an excellent one-line summary of what I was getting at.