XDebug, PHP and Sublime Text 2 on Ubuntu 12.04

Getting Xdebug for PHP working with Sublime Text 2 is slightly tricky.

Most of the instructions are at https://github.com/Kindari/SublimeXdebug - these notes are just a bit extra as a reminder to myself.

After installing Xdebug with “sudo apt-get install php5-xdebug”, you have to add some xdebug  settings in /etc/php5/conf.d/xdebug.ini:

zend_extension = /usr/lib/php5/20090626+lfs/xdebug.so
xdebug.remote_enable=On
xdebug.remote_host="localhost"
xdebug.remote_port=9000
xdebug.remote_handler="dbgp"
xdebug.remote_autostart=1

In ST2, you use SHIFT+F8 to get the Xdebug menu, and select “Start debugging”. In your browser session, add a url parameter like this:

?XDEBUG_SESSION_START=sublime.xdebug

The page request should hang while the debugger has control – if nothing’s happening in ST2, you’ll need to check the console window by using ”CTRL`”.

You may get this output:

Traceback (most recent call last):
File "./sublime_plugin.py", line 362, in run_
File "./Xdebug.py", line 553, in run
File "./Xdebug.py", line 97, in read
File ".\xml\dom\minidom.py", line 1927, in parseString
File ".\xml\dom\expatbuilder.py", line 32, in <module>
File ".\xml\parsers\expat.py", line 4, in <module>
ImportError: No module named pyexpat

In which case you’ve got the problem with Python versions – 2.7 is the default on Ubuntu 12.04, and ST2 and the Xdebug plugin are using Python 2.6.

So follow the instructions at the bottom of the README at https://github.com/Kindari/SublimeXdebug

On Ubuntu 12.04, Python 2.6 isn't available, so here's what worked for me:

    * Download python2.6 files from <a href="http://packages.ubuntu.com/lucid/python2.6">Ubuntu Archives</a>
    * Extract the files: dpkg-deb -x python2.6_2.6.5-1ubuntu6_i386.deb python2.6
    * Copy the extracted usr/lib/python2.6 folder to {Sublime Text directory}/lib

(I actually copied the usr/lib/python2.6 directory to /usr/lib/python2.6, and symlinked to there from {Sublime Text directory}/lib)

Then restart ST2 and try again. Once you hit a breakpoint, it should look like this, with the Xdebug Context and Xdebug Stack windows showing content:

Running SauceLabs Selenium test suite locally with PHPUnit

SauceLabs provide a great hosted Selenium service for cross-browser testing in the cloud, which allows you to run a Selenium test suite against multiple browsers using the SauceLabs API and SauceConnect.

They also provide integration with PHPUnit, so that you can plug your SauceLabs Selenium tests directly into your PHPUnit test suite.

Sometimes, though, you want to run the same Selenium test suite against a single browser on your local machine, before you run the full thing against all the browsers on SauceLabs.

However, the SauceLabs PHPUnit integration requires you to extend their special base class, which they’ve added to their custom PHPUnit library (which you need to have installed instead of the standard PHPUnit). That base class assumes that the test suite is always going to run against the remote SauceLabs API.

To run a Selenium test suite locally, you need to be extending the standard PHPUnit Selenium base class instead of the custom SauceLabs one – but you don’t want to duplicate all your tests in two class hierarchies.

How can you have a class where the parent class can be one of two different things, depending on some config setting?

The answer is by using  PHPs “class_alias” and a custom autoloader (note, “class_alias” is only available in PHP from 5.3.0 onwards). It works something like this..

Instead of extending either the special SauceLabs base class or the PHPUnit selenium base class directly, you define two different intermediate base classes – called RemoteSeleniumTestCase and LocalSeleniumTestCase – which each extend one of the base classes:

// RemoteSeleniumTestCase.php
class RemoteSeleniumTestCase extends PHPUnit_Extensions_SeleniumTestCase_SauceOnDemandTestCase 
{
    function setUp() {..}    // special setup for SauceLabs 
}
// LocalSeleniumTestCase.php
class LocalSeleniumTestCase extends PHPUnit_Extensions_SeleniumTestCase
{
    function setUp() {..}    // standard setup for local Selenium
}

The your actual test suite extends a (virtual) class – called DynamicSeleniumTestCase:

// your selenium test cases
class HomeTest extends DynamicSeleniumTestCase
{
    function testHomePage()
    {
        // check home page is there
        $this->open('/');
        $this->assertTextPresent('Welcome to BigCo');
   } 
}

So, where does DynamicSeleniumTestCase come from?

That’s where class_alias and the autoloader come in – you define a custom autoloader, which will alias DynamicSeleniumTestCase to one or other of your intermediate base classes, based on the value of an environment variable (I’ve used RUN_SELENIUM_REMOTE):

spl_autoload_register(function($class) {
    if (strcasecmp($class, 'DynamicSeleniumTestCase') === 0) {
        $remote = getenv('RUN_SELENIUM_REMOTE');
        if ($remote) {
            require_once 'RemoteSeleniumTestCase.php';
            class_alias('RemoteSeleniumTestCase', 'DynamicSeleniumTestCase');
        } else {
            require_once 'LocalSeleniumTestCase.php';
            class_alias('LocalSeleniumTestCase', 'DynamicSeleniumTestCase');
        }
    }
}, true, true);

Then, to make your test suite run against your local Selenium setup and local browser, just do

export RUN_SELENIUM_REMOTE=0

before firing off the build, and to run the same thing against the SauceLabs server, do

export RUN_SELENIUM_REMOTE=1

Genius or insanity?! You decide.

 

Upgrading PEAR if Guzzle install fails

I had a problem with the PEAR installer with the Guzzle package (a generally excellent PHP HTTP framework).

A channel discover would give the following error:

root@ip-10-56-47-23:~# pear channel-discover guzzlephp.org/pear
 Discovering channel guzzlephp.org/pear over http:// failed with message: channel-add: Cannot open "http://guzzlephp.org/pear/channel.xml" (File http://guzzlephp.org:80/pear/channel.xml not valid (received: HTTP/1.1 404 Not Found
 ))
 Trying to discover channel guzzlephp.org/pear over https:// instead
 Discovery of channel "guzzlephp.org/pear" failed (channel-add: Cannot open "https://guzzlephp.org/pear/channel.xml" (Connection to `guzzlephp.org:443' failed: Connection refused))

Strangely, I could fetch the channel.xml file manually and add the channel. But even then, the install would fail (with similar messages about 404 errors).

The solution seemed to be upgrading PEAR – I was running version 1.9.0 (check it with “pear -V”), so I upgraded it:

root@ip-10-56-47-23:~# pear upgrade pear

after which I had version 1.9.4. Then I tried the channel discover again, with much better results:

root@ip-10-56-47-23:~# pear channel-discover guzzlephp.org/pearAdding Channel "guzzlephp.org/pear" succeeded
 Discovery of channel "guzzlephp.org/pear" succeeded

 

PHP 5.3.0 and the “global” keyword

This had me stumped for a while – using some 3rd party code with PHP 5.3.0, the “global” keyword didn’t seem to work any more (references to the supposedly “global” variable from within functions always gave NULL)..

You have to declare the variable as “global” before setting it in the outer scope – then it works again.

So, this used to work (but doesn’t under PHP 5.3.0) :

$util = new Utility();
global $util;
function show() {
    global $util;
    echo "$util->version";
}
but if you swap the lines of the declaration of $utils, then it does work under PHP 5.3.0 :
global $util;
$util = new Utility();
function show() {
    global $util;
    echo "$util->version";
}