Debugging CLI PHP with Zend Server and PDT on Linux and Mac

I'm working on a small PHP application and a big part of it are some CLI scripts which will be executed in the background. Some of these scripts are quite complex, and I got to a point where I need to use a debugger in order to figure out what's going on.

I started hacking around with my locally-installed Zend Server CE and Zend Studio. I always knew how to manually start CLI debug sessions with Zend Studio (well, I knew, but forgot ;-) ), but then I figured, why not write a small shell script to automate the process, and learn a little about the Zend Debugger protocol on the way?

Here is what I did:

First, create the following shell script. I placed it at /usr/local/zend/bin/php-dbg (alongside the other Zend Server executables, which if you use Mac OS X will be at /Applications/ZendServer/bin):

CODE:
  1. #!/bin/sh
  2.  
  3. # Wrapper script for debugging PHP CLI scripts with Zend Studio
  4. # Tested with Zend Server 4.0.0 Beta and Zend Studio for Eclipse 6.1.1
  5. # Shahar Evron [shahar.e at zend], 2009-02-20
  6.  
  7. # Defaults
  8. DFLT_PORT="10137"
  9. DFLT_HOST="127.0.0.1"
  10. DFLT_PARAMS="debug_fastfile=1&use_tunneling=0"
  11.  
  12. # Load Zend Server environment variables
  13. . /etc/zce.rc
  14.  
  15. # Did the user specify the debug host / port?
  16. if test "x$DEBUG_HOST" != "x"; then
  17.   if test "x$DEBUG_PORT" != "x"; then
  18.     QUERY_STRING="&debug_port=$DEBUG_PORT"
  19.   else
  20.     QUERY_STRING="&debug_port=$DFLT_PORT"
  21.   fi
  22.  
  23.   QUERY_STRING="$QUERY_STRING&debug_host=$DEBUG_HOST&$DFLT_PARAMS"
  24.  
  25. # If no host/port were specified, try to auto-detect
  26. else
  27.   QUERY_STRING=`wget http://localhost:20080/ -O - 2> /dev/null`
  28.   if test $? -ne 0; then
  29.     # Fall back to defaults
  30.     echo "Unable to auto-detect Zend Studio settings, using defaults">&2
  31.     QUERY_STRING="&debug_port=$DFLT_PORT&debug_host=$DFLT_HOST&$DFLT_PARAMS"
  32.   fi
  33. fi
  34.  
  35. DBG_SESS_ID=`date +%s`
  36. QUERY_STRING="start_debug=1&debug_stop=1$QUERY_STRING&debug_session_id=$DBG_SESS_ID"
  37.  
  38. QUERY_STRING=$QUERY_STRING $ZCE_PREFIX/bin/php -c $ZCE_PREFIX/etc/php.ini $@

Going over this code might teach you some surprising things about how Zend Debugger and Zend Studio talk to each other ;) I'm not going to go into the details now, but if you have questions feel free to ask.

Next, make this script executable - just run 'chmod +x <path-to-script>' - and you're good to go.

Here is how to use it:

  • If you have PDT or Zend Studio running locally (on the same machine as the server), just run:

    # /usr/local/zend/bin/php-dbg <script you want to debug>

    That would just work in most cases - if it works you can stop reading now ;-)
  • If you are running the script on a server, but your PDT / Zend Studio is on a different machine (in the same LAN - no NAT or firewall!) you can simply specify the IP address or host name of the machine that runs PDT / Zend Studio as the DEBUG_HOST environment variable. For example:

    # DEBUG_HOST=10.1.2.3 /usr/local/zend/bin/php-dbg <script you want to debug>
  • If you are running the script on a remote machine (as above) and your Zend Studio listens on a port other than 10137, you can also pass the DEBUG_PORT environment variable to override the default port.
  • Also, don't forget to make sure that the machine that runs your Zend Studio is in the list of allowed debugging clients. You can check it at the Zend Server GUI on Server Setup -> Debugger.
  • If you are running the script on a remote host and there's a firewall / NAT between you and the server (e.g. you are in an office LAN, trying to debug a script on a remote production machine which is not in your subnet) you'll probably need to use SSH remote port forwarding to forward connections to your PDT / Zend Studio. I won't get into how to do it right here - unless you insist.
  • If you want to only type 'php-dbg' when running instead of the full path, you can place the file in your $PATH (e.g. in /usr/local/bin) or even better, Add /usr/local/zend/bin (or /Applications/ZendServer/bin) to your $PATH - you can do that by adding the following line to ~/.bashrc:

    PATH=$PATH:/usr/local/zend/bin

Upon running the script, a debug session should simply pop-up in your PDT / Studio and you'll be able to debug. How cool is that?

BTW: This has been tested with Zend Server 4.0.0 beta1 and Zend Studio 6.1.1. It should work with other versions of Studio as well. In fact, it can also work without Zend Server as long as you have Zend Debugger installed - but why ruin a perfectly good plug?

If you improve the script or find bugs, let me know! Also, if you know how to get the same thing going with xDebug, let me know and I'll add it to the script.

Seven Things - Tagged by Ivo Jansch

"I was hopping along, minding my own business, when all of a sudden, up he comes…Cures me! One minute I’m a leper with a trade, next minute my livelihood’s gone!"

So apparently I was tagged by non other than the mighty Ivo Jansch, thus forced into some silly chain-blogging game. After managing to teach everyone in my life including the tech-clueless to never forward any chain mail to me (except for my grandma - I did get her to stop but then she sometimes forgets not to do it), I get bitten by the most tech-savvy people I might know :P

Well, I can't say no - especially after being titled "Product Manager and Smart Guy at Zend" (for a short period of time that was almost my official title BTW). Of course I'll play along! It's also interesting to see how far this goes :)

So here are seven things about me you might or might not know:

  • I never went to university - I've noticed some people state what their major was etc. - so, I never went to university. I never got the chance. While everyone else was in university, I was busy doing other things like learning to write PHP :) I always tell myself I'll go in a year or to - but now that I've gotten into a position everyone assume I have at least a BSc, I don't want to spoil that :).
  • I never took any official CS training - I've frequently found it boring the way it was taught in school. Don't get me wrong - I love CS and I sometimes find it exciting - but I enjoy it much more when I get to teach myself. I also tend to believe CS is nice but it's a tool - not really a "wisdom" I'd like to learn in university. I consider other fields like philosophy, history, biology or even mathematics to be far more worth studying. BTW I did take a couple of CS university courses during high-school - I flunked one of them ;)
  • I started programming when I was 7 or 8 - in BASIC. Well, it was nothing impressive but this is how I started. I then went through some Pascal and some C but never got to any level worth talking about. Then in high-school there was the web, and I started playing with it, first with HTML and then with Perl writing CGI stuff. Pretty much at the same time I started experimenting with Linux.
  • I did that while majoring in Cinematography. Yes - the only thing I did learn professionally was making movies, and I still have a lot of passion for it. I especially enjoyed directing, filming and editing. I did try working for some time on several junior technical positions in the local film industry (2nd assistant camera operator etc.) and did get my name in the credits of some movies (who were major in Israel - so there you have it Ivo ;) ) but working for the "industry" so to speak was not so great, and I decided to drop it.
  • Just like most male citizens of Israel I did my military service after highschool - and didn't get to touch a programmable computer for 3 years (I did get to touch some computers but they were not the kind you want to mess around with). I even almost became an officer (which means I could have spend more than those 3 years in the army) but I didn't run fast enough. I still spend a few days every year in reserve training, but sometimes they let me off the hook when I have to go to some PHP conference :)
  • When I got out of the army and back into the real world, Perl was gone and all of a sudden there was PHP everywhere. I found out I could write the same app I wrote in Perl in a month in about a week in PHP, and the rest is history. Back at the time I wrote my first PHP app - a web site which is still running today. I looked at the code a few months ago and almost puked.
  • Before working for Zend, I worked for a local ISP which as managed by the biggest Linux geek I've met (no wonder it's out of business by now) - that was a lot of fun. Before that, I did all sorts of things - I herded goats, I picked cherries and I grew long curly hair. I got to Zend by pure chance - I didn't even knew they were an Israeli company until I met a cousin of mine who told me he was working there!

So... those are my seven things!

And now, who to tag? I have very few candidates left - I hardly have any blogging friends who haven't already been tagged... So here are my people, hopefully they'll forgive me:

  • Nir Yariv - friend, family member, and one of the smartest (infrequent) bloggers I know (he's also the guy who got me into Zend).
  • Christer Edvartsen - fellow PHP coder, ZF contributor, and occasional (one a year or so) drinking buddy
  • John Coggeshall - Ex-Zender, Current CTO at ACS, PHP author, and well, another occasional drinking buddy
  • Stas Malyshev - PHP Internals Guru and Zend's 1st employee
  • Boaz Rymland- Another ex-colleague, Drupal expert and a certified social worker
  • Andi Gutmans - Fellow Zender and, wel... you all know him don't you?
  • Zeev Suraski - Well you know him too don't you?

Well... that's my list - let's see how many of them step up to the plate!

Oh - and here are the rules for any bloggers who decide to follow up:

  • Link your original tagger(s), and list these rules on your blog.
  • Share seven facts about yourself in the post - some random, some wierd.
  • Tag seven people at the end of your post by leaving their names and the links to their blogs.
  • Let them know they've been tagged by leaving a comment on their blogs and/or Twitter.

PHP Error Reporting Levels - WTF is 6134?

In PHP, the error reporting level (whether errors go to the log or to the screen or whatever) is determinded by the error_reporting INI directive (or at runtime using the error_reporting() function). Both take an integer as their value - and usually this integer is represented by error level constants like E_ALL, E_STRICT or E_USER_WARNING.

So in order to set the error reporting to anything but notices and strict errors, you would set something like this in php.ini:

CODE:
  1. error_reporting = E_ALL & ~E_NOTICE & ~E_STRICT

(actually, E_ALL does not really include E_STRICT but I put it here just to be more explicit)

This is actually great - one of the more easy to use and understand APIs in my opinion (yeah, I really like bitwise operations).

However, what I really hate is that sometimes I need to work with the integer value of the error reporting level (like 1 for E_ERROR or 84 for E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR) and it's very hard for me to remember what arbitrary numbers like 6134 mean.

So, today I wrote this tiny CLI script that helps me understand what an arbitrary error_reporting level integer might mean:

CODE:
  1. <?php
  2.  
  3. $errorLevels = array(
  4.         'E_COMPILE_ERROR'     => E_COMPILE_ERROR,
  5.         'E_COMPILE_WARNING'   => E_COMPILE_WARNING,
  6.         'E_CORE_ERROR'        => E_CORE_ERROR,
  7.         'E_CORE_WARNING'      => E_CORE_WARNING,
  8.         'E_ERROR'             => E_ERROR,
  9.         'E_NOTICE'            => E_NOTICE,
  10.         'E_PARSE'             => E_PARSE,
  11.         'E_RECOVERABLE_ERROR' => E_RECOVERABLE_ERROR,
  12.         'E_STRICT'            => E_STRICT,
  13.         'E_USER_ERROR'        => E_USER_ERROR,
  14.         'E_USER_NOTICE'       => E_USER_NOTICE,
  15.         'E_USER_WARNING'      => E_USER_WARNING,
  16.         'E_WARNING'           => E_WARNING,
  17. );
  18.  
  19. if (! isset($_SERVER['argv'][1])) {
  20.         fprintf(STDERR, "Usage: {$_SERVER['argv'][0]} <error_level>\n" .
  21.                         "  Where <error_level> is a PHP error reporting level\n");
  22.         exit(1);
  23. }
  24.  
  25. $level = $_SERVER['argv'][1];
  26.  
  27. echo "Error level $level includes:\n";
  28. foreach ($errorLevels as $k => $v) {
  29.         if ($level & $v) echo "\t $k \n";
  30. }
  31.  
  32. echo "\n";

To use, just run the script with a single value parameter, like so:

CODE:
  1. shahar.e@wintergreen ~ $ php error_level.php 6134
  2. Error level 6134 includes:
  3.      E_COMPILE_ERROR
  4.      E_COMPILE_WARNING
  5.      E_CORE_ERROR
  6.      E_CORE_WARNING
  7.      E_PARSE
  8.      E_RECOVERABLE_ERROR
  9.      E_USER_ERROR
  10.      E_USER_NOTICE
  11.      E_USER_WARNING
  12.      E_WARNING

Enjoy!

Good old ‘includes/common.php’ is back!

In the last couple of days I'm writing a small demo-like database driven PHP app - you know, the kind where you show how to put records of something into the DB, take them out, list, edit, etc. - pretty simple stuff.

The catch is that this is to demo pure PHP - no frameworks of any kind. No complex design patterns or paradigms (such as MVC), no rewriting rules.

I must say I haven't done this in a while. It feels good on one hand - remembering the power and simplicity of pure PHP. On the other hand it sucks ass - so many things (especially edge cases) you have to take care of yourself - things like execution flow, including that good old bootstrap file on the top of each script (yeah, each page in my app is a different script! remember those days?), manually taking care of layout and common HTML elements, validation - even manually writing so much SQL seems odd.

If you have some free hours this weekend, I suggest you try it - just to remember what it used to be like 5 years ago. It's a good way to appreciate (or not) the framework or library you're working with these days.