“CLI, the other SAPI” episode 1: introduction & basic usage

I’ve been using the CLI SAPI for a couple of years now and I’m still amazed about the difference in perspective it brings. In fact, the CLI SAPI is my chief tool for debugging and testing my PHP code. I find the subject that interesting that I decided to make a presentation about it in which I cover the different aspects, features and added value CLI brings along. This presentation is now in its final phase and while I’m waiting for the right occasion, I figured it wouldn’t be a bad idea to make a series of blog posts about the topic.

This is episode 1 in which I will explain what the power of the CLI SAPI is. I hope experienced developers agree with my opinion and honor CLI as an excellent concept. I also hope less experienced developers who have no clue of CLI can discover a new range of possibilities.

SAPI … schmapi?

First things first: what the hell is a SAPI? Wikipedia describes it as follows:

The Server Application Programming Interface (SAPI) is the generic term used to designate direct module interfaces to web server applications

Basically it’s just the way your system interacts with PHP. Most PHP developers use the Apache SAPI because they want to run their application on a webserver and use the HTTP protocol to interact with a webbrowser. This concept is the “de facto” standard for web-based applications because it’s so easy to perform client/server actions.

The Command Line Interface

Unfortunately the web-based approach gave PHP the reputation of just being a scripting language which was ideal for dynamic websites. For those of you out there still making these statements: “wake up and smell the concrete”, because things have changed. It’s not like CLI is the next big thing, no … it has been there for a while, but not everyone has been using it. If you’re using Symfony or PHPUnit you probably have some experience with executing PHP applications in an environment other than a webserver.

Now, what is that CLI thing I’m constantly talking about. PHP.net defines it as follows:

As of version 4.3.0, PHP supports a new SAPI type (Server Application Programming Interface) named CLI which means Command Line Interface. As the name implies, this SAPI type main focus is on developing shell (or desktop as well) applications with PHP.

So that client/server approach via a webserver is no longer a must: one can create small scripts that can be executed on the local system just like any other “real” programming language. Most programming languages reach a certain point where HTTP support is needed, but ironically PHP did it in the exact oposite way: by offering CLI support PHP proved its maturity.

When to use it

Before I answer the how to use it question, it’s important to explain when to use it. Shell scripts are especially useful in the following cases:

  • Executing batches
  • Interacting with other commands (e.g. using pipes)
  • Executing time specific actions via crons
  • Performing process control

How to run the script

Instead of calling a URL, we execute the php binary and add the filename of the script as the first argument. Additional arguments can be passed as well. It’s even possible to run your script interactively and read the input lines.

$ php cli.php

Working with arguments

To access these arguments, PHP offers you the $_SERVER[‘argv’] array where the first element contains the first argument being the filename itself. Because arrays are zero-based, key 1 contains the first additional argument and key 2 the second one. If the flag register_argc_argv is turned on in your php.ini the local variables $argc and ‘$’argv” can be used

  • $argc: is an integer that contains the number of arguments
  • $argv: is an array that contains the value of these arguments

An example:

$ php cli.php arg1 arg2

The PHP script itself:

1
2
3
4
5
6
<?php
/**
 * Get the argument count and print out the value of each argument, including the script name
 */
echo "There are $argc number of arguments".PHP_EOL;
echo "Script ".$argv[0].", arg1: ".$argv[1].", arg2: ".$argv[2].PHP_EOL;

Working with input

Like real interactive command line applications, you can read input lines and parse them in your script. PHP also supports it and has a special stream wrapper for it called php://. When reading from the Standard Input you can use the following stream resource: php://stdin. The same thing works for output to Standard Output via php://stdout. Because these are streams, you can communicate with them through the standard fopen, fread and fwrite syntax.

If you want to read complete lines of input, it’s better the use fgets. In that case you no longer need to open a separate resource, but you can directly pass the constant STDIN which is a de facto resource. The same thing works for writing by directly calling the fwrite function and passing the STDOUT constant

An example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
/**
 * Read input from STDIN and print input with line numbers. 
 * Quit when blank rule entered.
 */
$line = 0;
do{
    $line++;
    $input = trim(fgets(STDIN));
    if(strlen($input) > 0){
        echo "$line# $input".PHP_EOL;
    }else{
        break;
    }
}while (true);

Piping

In the when to use section I mentioned interaction with other commands. This sounds quite vague, so a more detailed explanation is in order here. In Unix (and Linux as well) there is a mechanism to link the streams of simple commands in order to achieve a more complex goal. By linking them you get one big linked command. There is a real nice article on Wikipedia that covers this topic.

Let’s say your script reads data from a database and outputs the apropriate content based on a content identifier that was passed as an argument. If you use stdout for output, you can pass the output to another command as its input.

If we now would like to calculate in how many lines of the content the word yes was found, we can simply chain the PHP script with the commands grep and wc. Grep wil perform a search and wc will count the occurences. When we bring all of this together, we’d get the following command:

$ php cli.php 1 | grep yes | wc -l

Let’s explain the different steps:

  • Php cli.php 1 opens our custom script and assigns value 1 as the identifier of the content
  • The php script will query the database and get record 1. The output will go to the standard output
  • Grep catches the output of the PHP script and filters the lines that contain yes. Again the output is sent to the standard output
  • Finally wc wil take the output of the previous command as its input and by assigning option -l the command will count the lines and output them.

The Apache comparison

It was inevitable, but we do need a comparison with Apache, the most common PHP SAPI there is. The goal isn’t to bash the Apache SAPI, but to show which is the best tool for the job and those jobs tend to differ.

Stateful/stateless

In the wonderful world of IT there is often a need for a state. This can be the state of:

  • An session
  • An application
  • An order

HTTP is a stateless protocol: all input is read at once by sending a get or post request and the output arrives in a response message. In between there is no real link with the server application. In a stateful world there is a need to to bridge this gap and that’s where mechanisms such as sessions and cookies can serve as a workaround.

CLI however is stateful mainly because there is no client/server architecture to respect. This provides us with the luxury of performing multiple user I/O calls at once. Throughout the communication, the process remains active which means the state can be stored. To store stateful data it’s even sufficient to use a local variable.

Please keep in mind that your safe little world of PHP website developement no longer exists. So, don’t use sessions or cookies. Don’t format your output in HTML either. That’s why not all code is suited to be executed in CLI mode. Please review and refactor your code accordingly.

Limitations

Another advantage of not depending on HTTP is that the most important limitation is no longer in effect. This is the maximum_execution_time setting. This means you can run your script without having to think about timeouts. This is really convenient when you want to execute large batches or if you want to run the script as a service. If you’re designing the script as a service, you just create an infinite loop and perform your tasks in each iteration.

I/O: input/output

We already covered this topic, but it will be useful to compare input/output between Apache and CLI. When you print (via print, echo and other mechanisms) your output in Apache mode, all output is gathered and returned to the browser in a response message. Input is gathered from the client and sent to the webserver in a request via get or post. So all I/O is bundled together.

In CLI mode we use stdin and stdout to get our input. Additional command line arguments can be used as well. Your I/O is no longer strictly bundled, all output is directly thrown to stdout. If you perform multiple output calls, all ouput will be displayed on screen immediately. The same thin works with input. Of course, when you only perform one output call, output is bundled just like in Apache mode.

Current working directory

Please be aware of the fact that in CLI mode the current working directory isn’t changed. This can cause issues when working with paths. If the current working directory of your terminal sessions is the root’ (/)’ and your script is located in /home/script, you need adapt your script to make it work with a variable cwd”.

Using your include_path is an option, but I prefer using ”dirname(__FILE__)” to ensure I call the right directory.

Overhead alert

Some people are completely stuck in the world of websites. Even when they execute cron’s or batches, they still use HTTP. A common overhead fail is calling a local script via HTTP bij using lynx:

* * * * * lynx -dump http://my.local.domain.ext/cron.php

Wouldn’t it be so much easier to just perform the following call?

* * * * * php -q /home/cron.php

On the next episode

Now that we are familiar with the concepts we can try stepping it up. In the next episode I will cover the php binary itself with all of its options. If you think running basic scripts is all that the binary can do, you’re dead wrong. Please tune in for my next blog post which will probably arrive next week.

2 Comments

Leave a Reply

Your email is never shared.Required fields are marked *