PHP CLI and Local Setup Headaches

Posted on Posted in CLI, PHP

Ok, I had some trouble running across some Linux setup stuff when it comes to running cron jobs for PHP scripts that I wanted to pass along in a helpful post.

We had some cron jobs that were running weekly and monthly for a long time in the old CGI environment. They were working just fine or at least we thought they were. Well, we had a one time dump of some very large files that we needed done so we wrote the script, tested it on a small subset, and when we were satisfied, we set a job to run at 3 AM EST to minimize any interruptions for the majority of our customers.

Well, needless to say the job didn’t complete. But the most frustrating part of this all was that the cron job ran! There were no errors that showed up in the cron logs or anything! What the hell is going on?

First thing to realize is that the PHP version on our Linux box has been upgraded a couple of times over the past year (currently 5.6). We used managed hosting with Rackspace and needless to say we had them do it. Somewhere along the line, default for PHP command line (which is how the cron jobs run) defaulted from CGI to CLI.

So the first thing we learned is that Rackspace if a cron job fails, actually spits the STDERR to a LOCAL email for the user that ran the job. I won’t tell you the user but it wasn’t webuser and it wasn’t root. The user had sufficient permissions to run the cron because we were able to SSH into the server and run the job manually. It was at this point that I realized that none of my regular cron jobs had run properly OR at least the ones that rely on PHP CLI.

So analyzing the error we find this:

Fatal error: require_once() [function.require]: Failed opening required

Now, the script itself was running fine via the browser. We did numerous tests with the pathing of the files to be included in the script and everything worked fine…. in the browser. Now, here is where I started to do a lot of research and found out that CLI scripts have some major changes to the old CGI SAPI.

  • Unlike the CGI SAPI, no headers are written to the output.
  • There are certain php.ini directives which are overridden by the CLI SAPI because they do not make sense in shell environments: (i.e. infinite execution time)
  • The CLI SAPI does not change the current directory to the directory of the executed script. (boom, this is the big one)

Hence that is the big one which is why I said boom. In browser environments and older CGI versions of the shell scripts, you could safely assume that no matter where you executed the script, everything was relative to the first executed script. But in CLI that is NOT the case. Everything is actually relative to WHERE the script is called from.

There is a great example for what this means at the bottom of this manual page here.

So what’s the workaround for this? One, in scripts you know will be running CLI, you can put the full path to the required files, no relative pathing using __DIR__ or dirname(__FILE__). Or you can use something like the following to get back to relative pathing if you are used to that.

chdir( dirname ( __FILE__ ) );

So I hope this somehow saves someone time and the headaches I went through trying to find everything wrong with the problem.

On a complete side note. I upgraded my local mac version of PHP using http://php-osx.liip.ch/. What actually happens is this installs a second binary and you have to point the php CLI to that version if you are using local CLI (which you should for composer and PHPUnit). You can do this by adding an EXPORT to your bash profile.

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

And remember this goes in your .bash_profile or .profile if you are using some other scripting terminal interface. I may have a lot more to add to the terminal discussion when it comes to automation for stuff like git or aliases to common commands. As well as writing bash scripts for automating common tasks (I know Grunt does this already 🙂 but I will save that for another post.

Again, I hope someone finds this helpful and that all the steps are there for you to diagnose your own problems and resolve any weird errors/non-errors. Hopefully you won’t have to go through all the trouble I did and luckily everything was “non-essential” or at least I had a way to get down what I needed to get done, albeit less automated, but hey programming is just problem solving!