Simple System Maintenance with PHP-CLI
Web development is a world of rapid code modifications, instant
deployment and immediate updates. Of course, maintaining the product of
this web development can be as hectic as the web development is agile;
with manual backups, quick database edits and tedious data migration
and re-entry. In this tutorial, I’ll show you how to use PHP-CLI, the
command line interface for PHP, to get your maintenance under control
using the technologies you’re already familiar with. By the time you’ve finished this tutorial, you’ll be able
to build human-assisted backup systems, automatic report generators.
As you follow this tutorial, I’ll presume you’re familiar with the
basics of PHP, and have built practical software with it – this
tutorial is about maintaining that practical software. You don’t
actually need anything to maintain to follow this tutorial, however;
just read through and experiment with the code snippets.
As we’re working with PHP from command line, access to the command
line of a system with PHP is essential; if you don’t have this yet, see
the "Quick setup’ section below. Basic command line knowledge is highly
recommended as well.
Finally, being able to recognise a SQL query helps but is not at all
required; we’ll be interacting with a database later in the tutorial.
If you’re on a shared host, or an old server, you may not have
access to a console with PHP; testing with your local machine might be
easiest. If you’re on Windows, XAMPP Lite
is a great way to get up and running with a disposable web server with
PHP CLI. XAMPP is also available for Linux systems but you’re probably
better off with your distribution’s package manager – an apt-get php5
would do on Ubuntu/Debian, for example, and this will also put the
binaries in the right locations. To be able to run PHP by simply
calling the PHP binary at your console, you’ll need to add your PHP
directory to your PATH environment variable; try this guide for Windows and this guide for UNIX
if you aren’t familiar with this. Finally, depending on your PHP
configuration, you may find that you need to suppress the HTTP headers,
which obviously won’t be of much use on the console. Instead of the php used in this guide, try running php –q if you find you’re getting content-type or similar headers before your output.
We’ll do things a little differently to the traditional Hello World examples, as we’re assuming you’re already familiar with PHP. Pull up your text editor and type out the following (beware of copying, as additional characters may have snuck into the HTML; especially watch the quotes):
$name = fgets(STDIN);
echo "Hello, ".$name.’! The time is currently ".date("r");
Save this file as clitest.php, pull up your command line and cd to the directory in which you saved the file. Now comes the tricky bit.
I’m presuming you made sure PHP was in your PATH, and is therefore available by calling php, instead of typing out the absolute path to the PHP binary. To check, try this:
scyth@wc:~$ php –v
PHP 5.2.1 (cli) (built: Nov 28 2007 23:14:55)
Copyright (c) 1997-2007 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies
If you get a similar result, you’re all ready for PHP CLI. If your shell reports that it can’t find PHP, you may need to type out the absolute path – e.g. /usr/bin/php, /usr/local/bin/php, C:phpphp.exe (Windows) etc. The "Quick start’ section above should help you get PHP-CLI up and running if you haven’t got PHP yet. Now, we call the script we just wrote:
scyth@wc:~$ php clitest.php
When you run this, you should get a blank screen. No, your script hasn’t crashed, nor is it stuck on some highly complex floating point calculations relating to pi. Let’s take a look back at our script:
$name = fgets(STDIN);
This line tells PHP to read input from the standard input stream and put it in the variable $name. The script is simply waiting for you to type your name in. A more effective script would incorporate a "Please enter your name: " prompt at the start; here we’re just experimenting. So, head back to your console and enter your name, followed by the Enter/Return key.
scyth@wc:~$ php clitest.php
! The time is currently Wed, 16 Jan 2008 17:13:59 +1100draicone@wcultsrv:~$
The wrapping may confuse this a little, but there are three things to note here:
- Our name was output back to us.
- There was a character after our name recognised as a newline.
- There was a missing newline after our script output where the shell wrote its next prompt.
The first of these is good; the last two we’ll have to take care of. As we hit enter after we entered our name, PHP-CLI took this as part of the input. A simple call to trim() will solve this. We can also solve the missing newline problem by outputting a n (or your system’s equivalent) at the end of our script. So, back to our code:
$name = fgets(STDIN);
echo "Hello, ".trim($name).’! The time is currently ".date("r")."n";
scyth@wc:~$ php clitest.php
Hello, Akash! The time is currently Wed, 16 Jan 2008 17:21:47 +1100
So, just as we’re used to, we can develop perfectly normal PHP scripts, and bring their power to the command line.
A little more professional…
Let’s experiment with removing the ‘php’, as this is the ugly reminder we’re still on an interpreted web-intended scripting language. (Windows users are probably better of skipping over this section, although it’s an interesting read.) Any Unix system can achieve the clean call syntax you might have seen in Perl or Python with the shebang. First find out where your PHP binary is: calling which php at console should reveal this; mine’s /usr/bin/php. Then add the shebang (also known as the hash bang) line at the start of your script. The shebang line starts with a #!, followed by the absolute path to the binary that should interpret the rest of the file. In our case, this should look similar to #!/usr/bin/php. Now when we call the file we no longer have to specify the php part at the start. You can even remove the .php file extension! Finally, jump back to your console window and run ./clitest (or ./clitest.php if you didn’t remove the extension). Here’s my final result:
scyth@wc:~$ cat cli2
$name = fgets(STDIN);
echo ‘Hello, ‘.trim($name).’! The time is currently ‘.date(‘r’)."n";
Hello, Akash! The time is currently Wed, 16 Jan 2008 21:05:36 +1100
The shebang tells the system that the remainder of the file should be sent to the interpreter program defined in the shebang line. One of the lesser known facts about PHP CLI is that you can actually call the PHP binary and write out your PHP code, then execute it on demand (typically by entering Ctrl+D). It’s a great way to experiment, and functions just like PHP-CLI – in fact, it probably is PHP-CLI, depending on your configuration. The shebang works just like this – it takes your code and sends it through your PHP binary to be interpreted on the fly.
A note – make sure you’ve got execute permissions on the file. A sudo chmod +x mycliscript should do the trick.
Let’s start with some basic details of how PHP-CLI works. If you’ve experimented with the CLI before, you can probably skip over this section.
What is PHP-CLI?
Command Line Interfaces, or CLIs, essentially provide access to the system via a simple text-based command interpreter. As the commands are nothing but text, they can be handled programmatically by other applications. This makes them highly interoperable and flexible – for example, PHP could be executed by another application to provide some functionality only available in PHP; in fact, we’ll cover this point in detail in a moment.
Since PHP 4.3.0, PHP has by default come with a SAPI, a Server Application Programming Interface, named PHP-CLI. This interface was designed for running PHP scripts at the command line, opening new possibilities for uses of PHP.
PHP scripts for the CLI not only have all the power of your existing PHP scripts, they are also designed to interface with the system much closer than typical web pages. As a result, they’re well suited for managing systems, as well as bridging the gap between the sysadmins and developers. Developing for the command line is somewhat different to traditional web page development:
- User input can be handled at runtime, instead of requiring the user to set all options for the task to be executed before running the application/script.
- The user sees the output as it is generated
- Execution times can be well above the typical 1-2 seconds of a web page
- Output is as plain and unformatted as possible; in fact, output is sometimes designed to be deliberately predictable so that it can be used by other programs
- The physical location on the file system that the user is currently working with has added importance
We’ll cover the impact of these in a moment.
But why PHP?
A question commonly asked of PHP-CLI is why it exists in the first place. Perl, for instance, has long been used in similar situations, and is arguably a more effective choice for building command line utilities. PHP was built with web pages in mind and its feature-set (and, therefore, part of its overhead) reflects this. There are some very compelling reasons to use PHP in CLI mode, however:
- You already know the language, and you’re a web developer, not a Perl/Bash/Awk scripter
- It integrates well with existing PHP web applications
- You can automate your web application’s time-consuming maintenance tasks
- You can reuse existing code, of which PHP has arguably one of the largest repositories
I’m not advocating entirely replacing Bash scripts with PHP-CLI, and if you’re serious about building command line utilities you should certainly explore Perl, but for the typical web developer being able to use the language you know and love (or not) is the best bonus.
Differences with normal PHP
PHP-CLI scripts function almost identically to the standard PHP scripts you’re used to. In fact, if you’re using PHP under windows and your PHP executable is in your system’s PATH, you can call your existing PHP scripts at command line already, with the simple command php myfile.php. There are a few basic differences in the scripting environment that you should be aware of; all are documented in the PHP manual and I encourage you to briefly read through this manual page before proceeding. There are a few important things to note:
First, the STDIN and STDOUT streams handle input and output (although basic echo/print will still work for output). A simple call to fgets(STDIN) allows you to take input from the user, which may be useful in accomplishing various tasks such as authentication and task direction. As a matter of good practise, you should use fwrite() on the STDOUT and STDERR streams for your general output and errors respectively.
Second, the working directory of the script – available from getcwd() – reflects the location you called the script from, not the location the script’s main file itself resides in. That is, PHP does not change the current working directory for the purposes of your script, and this creates immense potential for context-sensitive scripting.
Let’s examine this before moving on. What if you had a number of directories you wanted to index for your new photo album, but needed to hunt around your filesystem for them first? No matter – just put a PHP-CLI script in your path, roam around your console, cd‘ing to different directories, and call add_to_index when you come across another folder, where add_to_index is a very simple PHP script to add the current folder (from getcwd()) to a MySQL database. Your existing web applications can then tap into this data store and publish your new photos regularly. Sure, you could just as well achieve this in Perl or Python – maybe even a shell script – but what if you’re already familiar with PHP? PHP-CLI takes all your existing PHP skills and gives you a whole new world to explore.
Finally, the php.ini directive register_argc_argv is set to TRUE – as a result, the PHP variables $argc and $argv are set with appropriate values of command line arguments to your PHP script. That’s right: your PHP scripts can take command line arguments. And other applications that can run commands at the shell can call your PHP scripts, supply data to them and interact with all your existing PHP code. It’s often been said that, on a brand new GNU/Linux system, the basic utilities that come with the distribution are the most powerful. Now your PHP scripts can be equally powerful, without needing to learn a whole new language.
So, we’ve got the fundamentals of developing PHP-CLI scripts. Experiment with that a little further; especially try including your existing application’s config and database files and running some SQL queries against your application’s database. As this isn’t a tutorial on PHP programming, we won’t cover much specific code, as what you want to do with your code is up to you. With a bit of experimentation, however, you can easily unleash the full power of PHP-CLI.
So, what are we maintaining?
Chances are your existing web applications already have a number of ongoing maintenance tasks. If they don’t, maybe they should? Here are some common tasks PHP-CLI is used for:
- Removing old / expired entries from databases, say of cache files or message histories
- Resetting statistics / clearing logs
- Generating backup files
Many of these are handled manually by sysadmins, when they could be done just as well – maybe even more reliably – when automated with PHP-CLI. And some more interesting uses:
- Sending reports to stakeholders on site popularity and usage statistics
- Scanning for possible security breaches and locking down the site for “unexpected maintenance”
- Connecting to remote machines via SSH and SCPing sensitive data (e.g. userdata backups) through
The possibilities are only limited by what you can do in PHP.
Tips for CLI scripts
The PHP manual mentions how the maximum execution time is unlimited for CLI scripts. This should probably be a good hint that anything time consuming – a backup of a 200MB database, for instance – is certainly possible in a PHP-CLI script. In fact, sometimes it’s best done with a PHP-CLI script, as opposed to shell/batch scripting, which often results in functionality duplication – with a PHP-CLI script you can make use of existing PHP resources.
PHP-CLI scripts can be entirely automated, and do not necessarily have to call for user input, as we demonstrated in our example above – in fact, most of these scripts are designed to be called by other scripts and other applications, often serving as programmatic integration points between different areas of a technology stack. For example, a scheduled task of a PHP-CLI script could bridge the gap between a system’s task scheduling service and a web application’s data store.
This brings us to our final tip – scheduled tasks. On UNIX systems, crontab – a system for scheduling commands to be executed at scheduled intervals – is a common sight. With crontab, time-sensitive tasks can be achieved in web applications, which can prove highly valuable in certain situations. For example, you might want to process log data once a week, take a web application offline for a server restart at a certain hour of the day, or send out an auto-generated newsletter to users at the end of each month.
On Windows servers, crontab’s general functional equivalent is Scheduled Tasks. PHP-CLI also makes this task scheduling somewhat cleaner: the traditional hack for PHP task scheduling on a Windows server was to open a web browser at the URL of a script, and to schedule a task one minute later to close the web browser. This can just as well be done via CLI. In fact, PHP on Windows systems comes with a “php-win.exe’ binary, which is identical to PHP-CLI except that it omits the black DOS box, which can be somewhat distracting if the system is used as workstation as well as server.
Just to give you a feel of what can be achieved with PHP-CLI, here are some examples of scripts in action. Typically these demonstrate how functionality can be achieved in the real world; you can take these ideas and develop similar scripts that suit your own application.
This application is a chatroom. Instead of purging the database on every single page load – and, being a live chat room, there were many backend page loads – the database table was cleared regularly with a PHP-CLI script run with crontab. Here’s the code:
// time() – 86400 = 24 hours ago.
$db->query(“DELETE FROM `messages` WHERE `time` < “.time()-86400);
This script incorporates all the existing database code from the application – here, $db->query() is a method on an object created in the includes/database.php file of the main web application. By utilising this existing code, the script can achieve its intended functionality in just two lines of code.
Now, there are many maintenance tasks that can be achieved automatically, but sometimes it makes sense to have a real person directing a maintenance job. This might be a one off, like an assisted database dump. Here’s a sample of how I’ve achieved it in the past, making use of existing code in my application:
// Authenticate user with application’s auth system
fwrite(STDOUT, “Username: “);
$user = trim(fgets(STDIN));
fwrite(STDOUT, “Password: “);
$pass = trim(fgets(STDIN));
if (!EMC::auth($user,$pass)) die(“Bad login.”);
// Ask the user tables to exclude from the dump
fwrite(STDOUT, “Tables to exclude: “);
$tables = explode(“,’trim(fgets(STDIN)));
// Ask them where to save the dump
fwrite(STDOUT, “Filename: “);
$file = trim(fgets(STDIN));
// EMCDB::dump() returns SQL queries to recreate the database
$sql = EMCDB::dump(0, $tables);
// If the user doesn’t enter an absolute path, this is
// saved in the current path – useful for sysadmins.
fwrite(STDOUT, “Dump complete.n”);
A few things to notice here. First, instead of echo/print we’re writing to STDOUT – this is the standard output stream, similar to STDIN, which we’ve already used for input; and STDERR, error output. Writing to STDOUT is just good practise, although there is some technical reasoning behind it. Next, we’re authenticating the user. We can’t just let anyone get an entire copy of our database so easily (although if an intruder has gained shell access, chances are they will anyway). Next, we’re saving the database dump to the current directory. This highlights the value of context-sensitive PHP-CLI scripts: I could call the script from my home directory to make sure I’ve instantly got access to it; from a protected web-facing directory to give trusted users access to it etc. Finally, EMCDB::dump() is calling the mysql dump utility via command line itself; it has a call to shell_exec(). That is, PHP-CLI scripts can interact with other command line utilities, and that’s probably the most power you’ll get from your PHP-CLI scripts all day.
Experimenting is the best way to unlock the power of PHP-CLI. Take your existing application code, throw together hacks in PHP-CLI and see what you come up with. Especially try integrating with other utilities available on your system; you can create strictly server-side mashups with excellent results here. If you work in a small office, consider canvassing your fellow staff members for ideas on how they’d like to simplify managing your company’s web application(s); you might just build something really useful and get a raise!
If you’d like to learn more, here are some good PHP-CLI resources:
- PHP manual’s page on PHP-CLI, essential knowledge for PHP-CLI development
- PHP’s program execution functions, useful for interacting with other programs
- IBM developerWorks’ Roger McCoy’s explanation of technical aspects of PHP-CLI
- Harry Fueck’s introduction to technical details of PHP on the command line