I have personally said some pretty harsh things about PHP over the years. It's hard to say nice things about a language which doesn't have namespaces (apparently they are going to be introduced in PHP 6). This has led to a standard library that eats up the global namespace. On my box at home there are over 1,345 functions defined globally when I run get_defined_functions(). The number of defined functions depends on which modules you have installed. That said, I have started using PHP again and I'm determined to make it a more pleasant experience this time. I have discovered several tools that make working in PHP less painful and I'd like to share them.

One of the things that really bugs me about PHP is that it doesn't tell me what led up to an error. In Python you get a traceback every time an exception is raised. The good news is that if you install the xdebug module you can get nice tracebacks of PHP errors. Problem solved.

When developing I need to be able to use a REPL (Read Eval Print Loop) so I can make sure my assumptions about how the language works are correct. PHP doesn't come with a REPL but fortunately the guys at Facebook made phpsh, which is a great tool. It says a lot that the people at Facebook needed a tool like this since they have to put up with PHP every day. After installing it you can load any PHP files and then run arbitrary PHP commands and see the output. It also pulls up documentation from a database and lets you reload external PHP files. I cannot understand how people develop in PHP without it.

Last but not least is testing. This is where some of PHP's design flaws start to show up. I am a proponent of Test Driven Development which is very hard to do in PHP. Why? Because in PHP you cannot redefine global functions. This means that any call to mysql_query is going to be a call to whoever defines mysql_query first. When refactoring code that doesn't use OOP it can be very hard to ensure the code is doing what you expect except through manual testing. I banged my head against this over the weekend and I came up with GlobalMock, a way to make it so PHP can do late binding of global calls. It does require that you slightly modify your code. Here is a simple piece of code before:

<?
mysql_connect('localhost', 'root', '');
mysql_select_db('test');

function get_uid($username) {
    $query = sprintf("select uid from users where username='%s' limit 0,1",
                     mysql_real_escape_string($username));
    $result = mysql_query($query);
    if (!$result) {
        return false;
    }
    list($uid) = mysql_fetch_array($result);
    return $uid;
}

And here is what it looks like after applying GlobalMock so runtime binding is possible:

<?
require_once('../global_mock.php');
$gm = GlobalMock::getInstance();

$gm->mysql_connect('localhost', 'root', '');
$gm->mysql_select_db('test');

function get_uid($username) {
    $gm = GlobalMock::getInstance();
    $query = sprintf("select uid from users where username='%s' limit 0,1",
                     $gm->mysql_real_escape_string($username));
    $result = $gm->mysql_query($query);
    if (!$result) {
        return false;
    }
    list($uid) = $gm->mysql_fetch_array($result);
    return $uid;
}

Now actual unit tests can be written:

<?
require_once('../test/simpletest/autorun.php');
require_once('../global_mock.php');

class TestUser extends UnitTestCase {
    function testInitialization() {
        $gm = GlobalMock::getInstance();
        $gm->testing();
        $gm->add_expected('mysql_connect',
                          new GlobalMockIgnore(),
                          true);
        $gm->add_expected('mysql_select_db',
                          array('test'),
                          true);
        require_once('user_testable.php');
    }

    function testGetUid() {
        $gm = GlobalMock::getInstance();
        $gm->testing();
        $gm->add_expected('mysql_real_escape_string',
                          array('angelo_luis_martin'),
                          'angelo_luis_martin');
        $gm->add_expected('mysql_query',
                          array("select uid from users where username='angelo_luis_martin' limit 0,1"),
                          'results_of_search');
        $gm->add_expected('mysql_fetch_array',
                          array('results_of_search'),
                          array('1'));
        $this->assertEqual(get_uid('angelo_luis_martin'), '1');
    }
}

If $gm->testing() isn't called the arguments will be passed to the globally defined function. I hope this helps make PHP easier to test and therefore less painful to use. If you want to give GlobalMock a spin the source is up for grabs on GitHub.