Recipe 20.12. Using a Debugger Extension


20.12.1. Problem

You want to debug your scripts interactively during runtime.

20.12.2. Solution

Use the Xdebug extension. When used along with Xdebug's remote debugger client, you can examine data structure; set breakpoints; and step into, out of, or over sections of code interactively.

20.12.3. Discussion

The Xdebug extension provides a number of helpful features to aid in a development effort, such as code profiling that is compatible with Kcachegrind. In this recipe, focus on Xdebug's interactive debugging capability. In order to follow along with this recipe, you need to be able to compile and install a Zend extension, which means permissions to edit php.ini on your system. PHP's dl( ) extension-loading function does not work with Xdebug. Finally, examples in this recipe are intended to work with Xdebug 2.0.0.

Installing the Xdebug extension is a straightforward procedure. You can build from source, or you can install using the pecl command:

% pecl install xdebug-beta

Once you have the extension compiled and installed, you need to edit your php.ini file with the full path to the xdebug.so module, such as zend_extension = /usr/lib/php/extensions/no-debug-non-zts-20050922/xdebug.so.

For interactive debugging, you need to download a copy of the Xdebug source. The bundled Xdebug application debugclient is not installed by default with the pecl install procedure. This is because in many cases, the xdebug.so module is installed on a remote server, while the debugclient tool is typically installed on a separate machine. However, there is nothing that prevents the xdebug.so module from running alongside PHP and the web server in a local test environment.

Once you've downloaded the Xdebug source code that corresponds to the version of the module that was installed with the pecl command, unpack the source and chdir to the debugclient directory of the distribution. Issue the following commands:

% cd debugclient % ./configure % make % sudo make install

The debugclient binary will install in /usr/local/bin. Test the installation by simply running the debugclient command. You should see something similar to:

% debugclient Xdebug Simple DBGp client (0.9.1) Copyright 2002-2005 by Derick Rethans. Waiting for debug server to connect.

The debugclient tool listens for a connection from an Xdebug-enabled PHP script on port 9000 by default. You can ask debugclient to listen on another port by using the -p switch, like this:

% debugclient -p port

Just make sure that the setting in your script, or your php.ini file, matches the port that your debugclient is listening on.

Interactive debugging with Xdebug can be triggered through a command-line invocation of a PHP script, or it can be started by passing the proper values to a web server running Xdebug and a PHP script. To start an interactive session on the command line, you need to set a few environment variables before triggering the script:

% export XDEBUG_CONFIG="idekey=session_name remote_enable=1" % php myscript.php

If you're debugging a web page running on a remote server, you just need to add XDEBUG_SESSION_START=name to the request URL. A browser cookie will be set with the name XDEBUG_SESSION. When the get or post variable of XDEBUG_SESSION_START is set or if the XDEBUG_SESSION cookie is present, the Xdebug extension will attempt to connect to the debugclient specified in php.ini's xdebug.remote_host value.

Once a connection is made to the debugclient, the steps that follow are largely the same. The primary actions taken during interactive debugging are listed in Table 20-3.

Table 20-3. Common Xdebug commands

Command

Description

run

Starts or resumes the script until a breakpoint is reached, or until the end of the script.

step_into

Steps into the next statement. If there is a function call involved, Xdebug will break on the first statement in that function.

step_over

Steps to the next statement. If there is a function call on the line you were on when you issued the step_over command, the debugger will stop at the statement after the function call in the same scope as where the command was issued.

step_out

Steps out of the current scope and breaks on the statement after returning from the current function.

stop

Ends execution of the script immediately.

set_breakpoint

Sets a new breakpoint on the debugging session.

property_value

Gets a property value.

context_get

Returns an array of properties in a given context at a given stack depth. If the stack depth is omitted, the current stack depth is used.


With those options in mind, it's time to try debugging an application. Example 20-1 shows an overly complex application for saying hello to a visitor. The point of the over-complexity is not to demonstrate how you should greet visitors to your web site; it's just to give us some options for stepping over function calls while debugging interactively.

Overly complex greeting application

<?php $user = 'Curly'; function sayHello($user, $greeting = 'Hello, %s!') {   if (!validUser($user)) {     $greeting = 'Hey! What are you doing here, %s?!';   }   printf($greeting, $user); } function validUser($user) {   // do some validation here   return true; } sayHello($user); ?>

We'll debug this program interactively after triggering it with a browser. See Recipe 20.16 if you need pointers on setting up a local test environment for this purpose. First, start up the debugclient:

% debugclient Xdebug Simple DBGp client (0.9.1) Copyright 2002-2005 by Derick Rethans. Waiting for debug server to connect.

Now, load up Example 20-1 in a file named overhello.php and visit it with a browser, making sure to append the correct Xdebug values to the query string'for example, http://localhost/overhello.php?XDEBUG_SESSION_START=foo. Your waiting debugclient should register the connection and output the following:

Connect <init fileuri="file:///Users/clay/Sites/overhello.php" language="PHP" protocol_version="1.0" app idekey="foo"><engine version="2.0.0beta5"> <![CDATA[Xdebug]]></engine><author><![CDATA[Derick Rethans]]></author><url> <![CDATA[http://xdebug.org]]></url><copyright><![CDATA[Copyright (c) 2002-2005 by Derick Rethans]]></copyright></init> (cmd)

You'll notice that your browser has not yet generated any output; it's waiting for you to continue with the debugging session. To do that, step into the program one line at a time. The DBGp protocol used by Xdebug requires that an identifier be sent with each request, so the following command will pass the identifier we've already started with back to the server:

(cmd) step_into -i foo <response command="step_into" transaction_ status="break" reason="ok"> </response> (cmd)

That response means that the Xdebug extension received your request to step forward a line, and has paused the execution waiting for the next debugging request. To confirm where you are in the execution, you may issue a stack_get request at any time:

(cmd) stack_get -i foo <response command="stack_get" transaction_><stack where="{main}" level="0" type="file" filename="file:///Users/clay/Sites/overhello.php" lineno="1"></stack></response> (cmd)

The response from stack_get tells us that we're in the {main} portion of the script; in other words, we're not inside of a function or a class. We're running though the main file, and we're on line number 1. Looks good; let's move on to another line:

(cmd) step_into -i foo <response command="step_into" transaction_ status="break" reason="ok"></response> (cmd)

debugclient again returns with an ok response, and sets the status to break again to wait for more instructions from us. So what's going on in this line?

(cmd) context_get -i foo <response command="context_get" transaction_> <property name="user" fullname="$user" type="uninitialized"> </property></response> (cmd)

We can see from getting the context that the variable $user has come into the picture. The DBGp protocol is very careful about returning the values of variables and will not do so unless explicitly asked. Even then, you'll see that the values are always Base64-encoded to protect debugclient from any unexpected data:

(cmd) property_value -n user -i foo <response command="property_value" transaction_ type="null"></response> (cmd)

We instruct Xdebug to give us the value of the variable $user, and it returns a null type. Why? Because the value of $user isn't actually set until after the second line; the debugclient is currently reading line 2 from the beginning. So issue another step_into command, and ask for the property_value again:

(cmd) step_into -i foo <response command="step_into" transaction_ status="break" reason="ok"></response> (cmd) property_value -n user -i foo <response command="property_value" transaction_id= "foo" type="string" encoding="base64"> <![CDATA[Q3VybHk=]]></response> (cmd)

There's the base64-encoded value of $user, but how do we know what it contains? In a separate terminal window, just run a quick decoding of that value:

% php -r "echo base64_decode('Q3VybHk=');" Curly

So far, so good. However, debugging a large program a line at a time like this could become a painful, time-consuming experience. So let's jump ahead to where the action is. To do that, we'll set a breakpoint and then run the program until it reaches that breakpoint:

(cmd) breakpoint_set -i foo -t call -m sayHello <response command="breakpoint_set" transaction_ ></response> (cmd)

The -t switch lets Xdebug know that we're setting a breakpoint at a call type, and the -m switch sets the break at the point where the sayHello function is called. The response sets a unique ID for this breakpoint that we (or a full-blown IDE implementing the DBGp protocol) could use to refer back to this breakpoint. Now we can run the program until it pauses again, and then get our bearings again:

(cmd) run -i foo <response command="run" transaction_ status="break" reason="ok"></response> (cmd) stack_get -i foo <response command="stack_get" transaction_><stack where="sayHello" level="0" type="file" filename="file:///Users/clay/Sites/overhello.php" lineno="5"></stack> <stack where="{main}" level="1" type="file" filename= "file:///Users/clay/Sites/overhello.php" lineno="16"></stack></response> (cmd)

In our example program, we know that the sayHello function is defined on line 5 and called on line 16, so this response is in alignment with what we know about the example program. The debugger ran ahead to where the function was called, and then returned to where the function was declared so that we can step into that function and see what's going on in there. Let's do that now:

(cmd) step_into -i foo <response command="step_into" transaction_ status="break" reason="ok"> </response> (cmd) stack_get -i foo <response command="stack_get" transaction_><stack where="sayHello" level="0" type="file" filename="file:///Users/clay/Sites/overhello.php" lineno="5"></stack><stack where="{main}" level="1" type="file" filename= "file:///Users/clay/Sites/overhello.php" lineno="16"></stack></response> (cmd) context_get -i foo <response command="context_get" transaction_><property name="greeting"  fullname="$greeting" address="60271800" type="string" encoding="base64"> <![CDATA[SGVsbG8sICVzIQ==]]></property><property name="user" fullname="$user" address="60264872" type="string" encoding="base64"><![CDATA[Q3VybHk=]]> </property></response> (cmd)

This sequence of commands shows us that the program is now on line 16, and the sayHello function has been called. We're at the beginning of line 5 of the script, looking at the call to sayHello, noting what was passed into it, and preparing to go into the if( ) block that checks whether the $user is valid by running the validUser( ) function. Since we know that our simple program isn't finished yet, let's skip over that block and not waste time running through the incomplete function. After stepping over, check again to see where we are:

(cmd) step_over -i foo <response command="step_over" transaction_ status="break" reason="ok"></response> (cmd) stack_get -i foo <response command="stack_get" transaction_> <stack where="sayHello" level="0" type="file" filename= "file:///Users/clay/Sites/overhello.php" lineno="8"> </stack><stack where="{main}" level="1" type="file" filename= "file:///Users/clay/Sites/overhello.php" lineno="16"> </stack></response> (cmd)

As you can see, we're now on line 8, after the if( ) block. Let's go ahead and finish up by letting the program run its course:

(cmd) run -i foo <response command="run" transaction_ status="stopped" reason="ok"></response> (cmd)

Finally, the run command returns a stopped status because the program ran the rest of the way through without encountering any additional breakpoints.

This is certainly a simple example, but you can see that it's possible to drill down into your application and see what's going on in real time with Xdebug. You may find that it is easier to use an Xdebug-enabled IDE, such as the free editor WeaverSlave, than it is to use debugclient. However, debugclient can provide a great deal of insight into your application in a hurry all by itself.

20.12.4. See Also

Documentation on Xdebug at http://www.xdebug.org/; on the DBGp protocol at http://www.xdebug.org/docs-dbgp.php; Xdebug-enabled editor WeaverSlave at http://weaverslave.ws/.




PHP Cookbook, 2nd Edition
PHP Cookbook: Solutions and Examples for PHP Programmers
ISBN: 0596101015
EAN: 2147483647
Year: 2006
Pages: 445

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net