All debugging and no testing makes the PHP programmer a dull boy

DZone 's Guide to

All debugging and no testing makes the PHP programmer a dull boy

· Web Dev Zone ·
Free Resource

Years ago I contributed to this debugging class, whose purpose is to display object that were contaminated by the Doctrine ORM:

 public static function dump($var, $maxDepth = 2, $stripTags = true)
        ini_set('html_errors', 'On');

        if (extension_loaded('xdebug')) {
            ini_set('xdebug.var_display_max_depth', $maxDepth);

        $var = self::export($var, $maxDepth++);

        $dump = ob_get_contents();

        echo ($stripTags ? strip_tags(html_entity_decode($dump)) : $dump);

        ini_set('html_errors', 'Off');

"Contaminated" here means that proxies were inserted and the whole Doctrine object graph was reachable via the field references of the interested domain objects. This meant using var_dump() and similar functions would result in a 1-kilometer log being printed.

When you're developing an ORM it's difficult not to dump complex objects, but today I try to limit the use of debugging tools as much as possible.

Debugging sucks, testing rocks -- Google Testing Blog

For example, last week I came across this nice tutorial on using remote debugging in your IDE and by using Xdebug. Basically, there is a remote port that is opened by Xdebug and that the IDE can send commands to halt the execution of a script at predefined breakpoints and resume it step by step, or inspect variables contents.

If your code isn't working, this solution seems nice: for example, you can load a form and submit it, and stop in predetermined points to see exactly which line of code is corrupting the data you sent. At the same time, you don't need to insert var_dump() and print_r() calls which would often break the syntax of responses:

    "some_json" : "value"
string(2) "en"

and in these cases alter the behavior of what you're debugging (feeling a bit Heisenberg-like now?)

But it has a lot of accidental complexity and some intrinsic properties of all debugging processes:

  • it can only be performed end-to-end, in a browser: you cannot isolate pieces of code like a single class or a small object graph and insert values in them, unless you write a separate script that reproduces the problem. Being able to write that separate script would mean that you don't need debugging capabilities at all as var_dump() suffices.
  • It lacks repeatability as to find out if you have fixed the problem you have to manually open a browser and click links or insert values in a form and check what happens. And you have to remember to take the exact same steps to reproduce the error.

By now you have already understood that I would always proposed automated end-to-end and unit tests as a way to substitute much of debugging. End-to-end tests can serve only to discover that a bug exists, while a unit test's job is instead to tell you exactly where the problem is. These tests are by definition isolated by global state like standard output, so you'll be free to insert var_dump() calls in them.

$ phpunit --filter JumpsToASecure Tests/Selenium2TestCase/URLTest.php
#!/usr/bin/env php
Shared strategy
PHPUnit @package_version@ by Sebastian Bergmann.

Configuration read from /home/giorgio/code/phpunit-selenium/phpunit.xml

.string(23) "https://www.example.com"

Time: 0 seconds, Memory: 7.00Mb

OK (1 test, 3 assertions)

You have the freedom to repeat the test as many times as needed at little cost; and to isolate only the interesting test cases, running the tests for the current class or only one of them.

Listen to your tests

This motto's meaning is that if you're having difficulties in writing automated tests, you don't have to immediately pick up the big cannons like manual testing or Selenium end-to-end tests but rather change your design to allow easier isolation. Whole series of patterns like Humble Object, Adapter and Repository are enabler for unit testing in the presence of a difficult-to-test environment like having to work inside a framework, the need for making HTTP requests or to talk to the database frequently.

If you're writing:

$ch = curl_init();

and having to watch three browser windows and logs to find out why your songs are not loaded in the database, is just the symptom of the need for an abstraction over HTTP calls.

We will always need some debugging capability, especially in legacy code that is not isolated from other processes, the filesystem and the standard input and output. But when you take a car to a mechanic he doesn't drive around the town to find the issue, he runs tests on its parts; if necessary, he disconnects them and run them in isolation.

Don't ditch Xdebug

That said, especially when dealing with legacy code, don't ditch Xdebug as it provides a few tools that can save your day at very little cost. Two I have seen frequently come to my rescue when in the jungle of old, untested code are:

  • xdebug.max_nesting_level will stop infinite recursions (by default at a stack trace depth of 100 nested calls, which is enough for most PHP code.)
  • xdebug.scream will disable the @ operator.
  • xdebug.auto_trace will write down a file containing every single function and method call performed by the PHP processes. It is a bit heavy, but it lets you discover the exact point where a script terminates in case some exit() calls are sprinkled through the code or a segmentation fault occurs.

Xdebug has also some nice features that have nothing to do with debugging, like PHPUnit's code coverage and profiling of code for performance improvement.


Less debugging and more automated testing will benefit you in the long run, and you can consider a long run of some minutes in this case; the first time you repeat a manual test you are already saving time. However, remember that only if you stay closer to the unit and functional levels of testing you can reap the benefits of isolation: to quickly discover bugs targets a few objects at a time or a whole component, but not the whole LAMP stack. Chopping a giant procedure into objects is one of the goal of legacy code refactoring.


Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}