PHP isn't only used as a web-based scripting language, it can also be used on the command line.
This talks explains the benefits of command line PHP. Additionally, process control using CLI PHP is explained.
1. CLI, the other SAPI
Using PHP on the command line in a Linux
environment
Thijs Feryn - thijs@combellgroup.com
2. Who am I?
• Thijs Feryn (http://blog.feryn.eu)
• Support manager at COMBELL
• Zend Certified PHP 5 developer
• I love the LAMP stack
• Working on my MySQL certification
• Open-source contributor
• Nerd/geek of some sort
4. Who’s COMBELL?
• Largest independent hoster in Belgium
• Founded in 1999 (a decade of COMBELL)
• Focus on premium/quality hosting
• More than 25 000 customers
• More than 100 000 domains
• More than 20 000 websites
• More than 1000 servers
• More than 800 resellers
Summarized: authorative partner for all
hosted activity
5. Who are you?
• Who’s a developer?
• Who’s a PHP developer?
• Who has ever used the PHP CLI?
• Who is used to working on Linux?
• Who has experience with process forking?
6. What’s up?
1. SAPI ... schmapi? (definition)
2. Common PHP SAPI’s (overview)
3. The CLI SAPI (how to use, possibilities, when to use)
4. Apache vs CLI (a comparison)
5. PHP binary options
6. CLI in action (some examples)
7. PCNTL == $fooDoo (the magic of process control)
8. Wake the daemons (putting it all together)
9. Q & A (the obligatory epilogue)
7. SAPI ... schmapi?
Definition
Wikipedia says: “The Server Application Programming Interface
(SAPI) is the generic term used to designate direct module interfaces to
web server applications”
In concreto: the SAPI is the way your server interacts with PHP
9. Common SAPI’s
Enabling the SAPI you need
• CLI is included by default
• Add the right parameter to enable the Apache
SAPI when compiling from source
./configure --with-apxs=/location/of/your/apache/module
10. The CLI SAPI
Definition
Php.net says: 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.
In Concreto: PHP scripts are no longer exclusively called via
the web browser. Using CLI, PHP scripts can be executed from the
command line of the server which offers a vast array of benefits
11. The CLI SAPI
When to use
• In cron’s
• For batches
• Process control
• Linux interactivity (e.g.: pipes)
12. The CLI SAPI
CLI 101
$p
hp
cli.
• Each CLI script is called via the PHP binary php
• Arguments can be passed
• Arguments can be interpreted via special variables
• Input can be passed via STDIN
• Output can be returned via STDOUT
• Errors can be returned via STDERR
• I/O via pipes is possible
13. The CLI SAPI
CLI 101: arguments
• Passing arguments
$ php cli.php arg1 arg2
• Interpreting arguments via $_SERVER
• $_SERVER[‘argc’] = counts the number of arguments
• $_SERVER[‘argv’][0] = scriptname (cli.php)
• $_SERVER[‘argv’][1] = first argument (arg1)
• $_SERVER[‘argv’][2] = second argument (arg2)
14. The CLI SAPI
CLI 101: arguments
If ‘register_argc_argv’ is enabled 2 extra local variables
are registered:
• $argc: counts the number of arguments
• $argv: array which stores the arguments
• $argv[0] = scriptname (cli.php)
• $argv[1] = first argument (arg1)
• $argv[2] = second argument (arg2)
15. The CLI SAPI
CLI 101: arguments & getopt()
• Gets command line arguments & values based on an input
variable containing all the options:
• Individual characters (= flags, do not accept values)
• Characters followed by colon (input value required)
• Characters followed by 2 colons (input value optional)
• Long options only work well in PHP 5.3
• Check out Zend_Console_Getopt, it has some nice features
similar to getopt()
$options = getopt(‘ab:c::’);
16. The CLI SAPI
CLI 101: input
• Reading from STDIN by opening a stream
$handle = fopen('php://stdin', 'r');
• Reading a single line from STDIN
$stdin = trim(fgets(STDIN));
17. The CLI SAPI
CLI 101: output
• Writing to STDOUT by opening a stream
$handle = fopen('php://stdout', 'w');
• Writing a single line to STDERR
fwrite(STDOUT,‘output’);
18. The CLI SAPI
CLI 101: errors
• Writing to STDERR by opening a stream
$handle = fopen('php://stderr', 'w');
• Writing a single line to STDERR
fwrite(STDERR,‘error’);
19. The CLI SAPI
CLI 101: piping
Using STDIN, STDOUT & STDERR streams you can easily
benefit from the piping mechanisms in Linux to interact
with other binaries
$ cat input.ext | php myCliScript.php > output.ext
$ php myCliScript.php | grep filterText > output.ext
20. The CLI SAPI
CLI 101: piping
You can also combine it with arguments
$ php myCliScript.php arg1 arg2 > output.ext
$ cat input.ext | php myCliScript.php arg1 arg2 arg3 | grep
filterText > output.ext
21. Apache VS CLI
State means everything
Apache
• HTTP is stateless a protocol
• Limited interactivity
• Sessions & cookies as workaround for these drawbacks
• Execution timeouts
• Limited packet size
22. Apache VS CLI
State means everything
CLI
• Controllable state
• More interactivity
• No (need for) sessions
• No execution timeouts
24. Apache VS CLI
Ins & outs: output
Apache CLI
HTTP output only STDOUT
Limited packet size STDERR
25. Apache VS CLI
CLI usage, Apache mentality
• Don’t use sessions or cookies, just use a local
variables
• Don’t “get” your input, “read” it
• No execution timeouts
• Bundled output (HTTP): in response message
• Distributed output (CLI): via STDOUT. Available
when needes
26. Apache VS CLI
CLI usage, Apache mentality
• OVERHEAD ALERT: don’t use HTTP via crons (e.g. via
lynx or wget), use the php binary.
• CLI mode doesn’t change the CWD (current working
directory). Be careful with path reference
• Use “dirname(__FILE__)”
• Use “chdir()”
27. PHP binary options
CLI options reference
Usage: php [options] [-f] <file> [--] [args...]
php [options] -r <code> [--] [args...]
php [options] [-B <begin_code>] -R <code> [-E <end_code>] [--] [args...]
php [options] [-B <begin_code>] -F <file> [-E <end_code>] [--] [args...]
php [options] -- [args...]
php [options] -a
-a Run interactively
-c <path>|<file> Look for php.ini file in this directory
-n No php.ini file will be used
-d foo[=bar] Define INI entry foo with value 'bar'
-e Generate extended information for debugger/profiler
-f <file> Parse and execute <file>.
-h This help
-i PHP information
-l Syntax check only (lint)
-m Show compiled in modules
-r <code> Run PHP <code> without using script tags <?..?>
-B <begin_code> Run PHP <begin_code> before processing input lines
-R <code> Run PHP <code> for every input line
-F <file> Parse and execute <file> for every input line
-E <end_code> Run PHP <end_code> after processing all input lines
-H Hide any passed arguments from external tools.
-s Display colour syntax highlighted source.
-v Version number
-w Display source with stripped comments and whitespace.
-z <file> Load Zend extension <file>.
args... Arguments passed to script. Use -- args when first argument
starts with - or script is read from stdin
--ini Show configuration file names
--rf <name> Show information about function <name>.
--rc <name> Show information about class <name>.
--re <name> Show information about extension <name>.
--ri <name> Show configuration for extension <name>.
28. PHP binary options
Run code
• Binary option: -r
• Meaning: parse & run PHP code that is passed as an
argument
$ php -r “echo quot;Give me some outputquot;;”
Give me some output
Example
$
29. PHP binary options
Interactive mode
• Binary option: -a
• Meaning: running PHP interactively in a true command
line environment
• Requirements: PHP compiled with readline support
$ php -a
$ php > echo quot;Give me some interactive outputquot;;
Give me some interactive output
Example
$ php>
30. PHP binary options
Define configuration
• Binary option: -d
• Meaning: define INI configuration settings at runtime
Example
$ php -d max_execution_time=20 -r “echo ini_get(quot;max_execution_timequot;);”
20
$
31. PHP binary options
Lint check
• Binary option: -l
• Meaning: perform a “lint” check on a PHP file. Validates
the syntax of a script. Errors are printed via STDOUT.
Shell return codes are “0” or “1”
• Notice:
• Doesn’t work combined with the “-r” option.
• Only checks for parsing errors & cannot retrieve
fatal errors (e.g. undefined functions)
32. PHP binary options
List modules
$ php -m
• Binary option: -m [PHP Modules]
xml
• Meaning: prints all loaded PHP & Zend tokenizer
standard
modules session
posix
pcre
overload
mysql
mbstring
ctype
Example [Zend Modules]
33. PHP binary options
Syntax highlighting
• Binary option: -s
• Meaning: format PHP code into highlighted HTML code
• Notice: doesn’t work with the “-r” option.
$ echo quot;<?php var_dump($_SERVER);quot; | php -s
<code><span style=quot;color: #000000quot;>
<span style=quot;color: #0000BBquot;><?php var_dump</span><span style=quot;color:
#007700quot;>();<br /></span>
</span>
Example
$
34. PHP binary options
Version information
• Binary option: -v
• Meaning: displays version information
$ php -v
PHP 5.2.4-2ubuntu5.3 with Suhosin-Patch 0.9.6.2 (cli) (built: Jul 23 2008 06:44:49)
Copyright (c) 1997-2007 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies
with Xdebug v2.0.3, Copyright (c) 2002-2007, by Derick Rethans
$
Example
35. PHP binary options
Function reflection
• Binary option: --rf
• Meaning: displays function API information
• Requirements: PHP must be compiled with “Reflection”
support
$ php --rf var_dump
Function [ <internal:standard> function var_dump ] {
- Parameters [2] {
Parameter #0 [ <required> $var ]
Parameter #1 [ <optional> $... ]
}
}
$ Example
36. PHP binary options
Class reflection
• Binary option: --rc
• Meaning: displays class API information
• Requirements: PHP must be compiled with “Reflection”
support
38. CLI in action
Read from STDIN + distributed output
<?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 quot;$line# $inputquot;.PHP_EOL;
}else{
break;
}
}while (true); Code
39. CLI in action
Read from STDIN + distributed output
$ php cli.php
this
1# this
is
2# is
my
3# my
output
4# output Output
$
40. CLI in action
Read from STDIN + bundled output
<?php
/**
* Read input from STDIN and print input with line numbers.
* Quit when blank rule entered.
* Bundle output and print at the end of the script.
*/
$line = 0;
$output = '';
do{
$line++;
$input = trim(fgets(STDIN));
if(strlen($input) > 0){
$output.= quot;$line# $inputquot;.PHP_EOL;
}else{
break;
}
}while (true);
echo $output;
Code
41. CLI in action
Read from STDIN + bundled output
$ php cli.php
this
is
my
output
1# this
2# is
3# my
4# output
$ Output
42. CLI in action
Working with arguments
<?php
/**
* Parse all arguments starting at index 1.
* If format is quot;--keyquot; or quot;-keyquot;, parse key and assign quot;truequot; as value
* If format is quot;--key=valuequot; or quot;-key=valuequot;, parse key and extract value
* Else, value is argument, key is argument index
*/
for($i=1;$i<$argc;$i++) {
if(preg_match('/^(-+)([a-zA-Z0-9_-]+)(=([a-zA-Z0-9_-])+)?$/',$argv[$i],
$matches)){
$key = $matches[2];
if(array_key_exists(4,$matches)){
$value = $matches[4];
} else {
$value = true;
}
} else {
$key = $i;
$value = $argv[$i];
}
echo quot;Key: $key - Value: $valuequot;.PHP_EOL; Code
}
44. CLI in action
Shell binary
#!/usr/bin/php
<?php
/** Code
* Print the current time.
* This binary directly uses the PHP binary.
* When the script has execute permissions it can be called as quot;./cliquot; instead of
quot;php cli.phpquot;
*/
echo quot;The current time is quot;.date('H:i:s').PHP_EOL;
$ chmod +x ./cli
$
Permissions
$ ./cli
The current time is 17:43:08 Call script + output
$
45. CLI in action
Linux interaction & piping
• Pass PHP code to binary using the “run” flag
• Print 10 lines
• Pipe output from PHP script as input for the “word count”
binary
• Add the “-l” flag to count the number of lines
• Output 10
$ php -r 'for($i=0;$i<10;$i++) echo $i.PHP_EOL;' | wc -l
10
$
46. CLI in action
Using getopt()
<?php
$shortopts = quot;quot;;
$shortopts .= quot;f:quot;; // Required value
$shortopts .= quot;v::quot;; // Optional value
$shortopts .= quot;abcquot;; // These options do not accept values
$options = getopt($shortopts); Code
var_dump($options);
$ php cli.php -a -v -f value
array(3) {
[quot;aquot;]=> bool(false)
[quot;vquot;]=> bool(false)
}
[quot;fquot;]=>string(5) quot;valuequot; Output
47. PCNTL == $fooDoo
The magic of process control
Php.net says: Process Control support in PHP implements the Unix style
of process creation, program execution, signal handling and process termination.
Process Control should not be enabled within a web server environment and
unexpected results may happen if any Process Control functions are used within a
web server environment.
Installation
Add “--enable-pcntl” configuration option when compiling PHP
48. PCNTL == $fooDoo
Forking is not a culinary term
• pcntl_fork() copies the program execution into a child process
• Workload can be distributed between parent & child(s) via PID
checking
• Allow “multithreadish’ parallel execution by forking multiple child
processes
• Use a shared resource to define the workload (e.g.: file, array, DB,
IPC, ...)
Wikipedia says: In computing, when a process forks, it creates a
copy of itself, which is called a quot;child process.quot; The original process is then
called the quot;parent processquot;. More generally, a fork in a multithreading
environment means that a thread of execution is duplicated, creating a child
thread from the parent thread.
49. PCNTL == $fooDoo
PCNTL voodoo/$fooDoo
• After forking, your logic is distributed in different PHP processes
• Forking too many childs can cause performance issues
• When using infinite loops, build in sleeps to avoid performance
issues
• Only use PCNTL in CLI mode
• Be absolutely sure you master & control your child processes
• Avoid zombie processes
• Be able to kill your child processes at any time
• Only kil your own processes
Be very careful when forking child processes, because there are
risks involved (hence the term $fooDoo)
50. PCNTL == $fooDoo
Forking example
<?php
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
} else if ($pid) {
echo quot;[parent] I am the parent process and my child process has
PID $pidquot;.PHP_EOL;
echo quot;[parent] Waiting for child terminationquot;.PHP_EOL;
pcntl_wait($status);
echo quot;[parent] Exitingquot;.PHP_EOL;
} else {
for($i=0;$i<10;$i++){
echo quot;[child] Loop $i, sleeping for a second ...quot;.PHP_EOL;
sleep(1);
}
echo quot;[child] Exitingquot;.PHP_EOL;
exit;
}
53. PCNTL == $fooDoo
Signals
Wikipedia says: A signal is a limited form of inter-process
communication used in Unix, Unix-like, and other POSIX-compliant
operating systems. Essentially it is an asynchronous notification sent to
a process in order to notify it of an event that occurred. When a signal
is sent to a process, the operating system interrupts the process'
normal flow of execution. Execution can be interrupted during any non-
atomic instruction. If the process has previously registered a signal
handler, that routine is executed. Otherwise the default signal handler is
executed.
54. PCNTL == $fooDoo
Signals
• Communication between 2 processes
• Process sends asynchronous notification of an event
• Signal handlers can be used to execute custom logic
• Otherwise default signal handlers
55. PCNTL == $fooDoo
Signals: SIGTERM
• SIGTERM is used to terminate a process in a ‘nice’ way
• Much more gentle than SIGKILL
• Allows cleanup and closure of process
• Issued via ‘kill’ (no kill -9)
56. PCNTL == $fooDoo
Signals: SIGINT
• SIGINT is used to terminate a process via interruption
• Stops program execution just like SIGTERM
• Issued by a terminal (via CTRL+C)
57. PCNTL == $fooDoo
Signals: SIGCHLD
• SIGCHLD is sent to the parent when a child process is
terminated
• Common when using process forking
• SIGCHLD is by default ignored and a zombie process is created
• Only sent when the parent issues a ‘wait’ call (avoids zombies)
58. PCNTL == $fooDoo
Signals: example of SIGTERM & SIGINT
<?php
declare(ticks = 1);
function sig_handler($signo)
{
switch ($signo) {
case SIGTERM:
echo quot;SIGTERMquot;.PHP_EOL;
exit();
break;
case SIGINT:
echo quot;SIGINTquot;.PHP_EOL;
exit();
break;
}
}
pcntl_signal(SIGTERM, quot;sig_handlerquot;);
pcntl_signal(SIGINT, quot;sig_handlerquot;);
sleep(100);
59. PCNTL == $fooDoo
Signals: example of SIGCHLD
<?php
declare(ticks = 1);
$max=5;
$simultaneous=0;
$childId=0;
function sig_handler($signo) {
global $simultaneous,$childId;
switch ($signo) {
case SIGCHLD:
$simultaneous--;
echo quot;SIGCHLD received for child #$childIdquot;.PHP_EOL;
echo quot;Decrementing to $simultaneousquot;.PHP_EOL;
}
}
pcntl_signal(SIGCHLD, quot;sig_handlerquot;);
60. PCNTL == $fooDoo
Signals: example of SIGCHLD
for ($childId=0;$childId<10;$childId++){
$pid=pcntl_fork();
if ($pid == -1) {
die(quot;could not forkquot;);
} else if ($pid) {
if ( $simultaneous >= $max ){
pcntl_wait($status);
} else {
$simultaneous++;
echo quot;Increment to $simultaneousquot;.PHP_EOL;
}
} else {
echo quot;Starting new child #$childId quot;.PHP_EOL;
echo quot;Now we de have $simultaneous simultaneous child processesquot;.PHP_EOL;
sleep(rand(3,5));
exit;
}
}
61. PCNTL == $fooDoo
POSIX functions
Wikipedia says: POSIX or quot;Portable Operating
System Interface for Unixquot; is the collective name of a
family of related standards specified by the IEEE to
define the application programming interface (API),
along with shell and utilities interfaces for software
compatible with variants of the Unix operating system,
although the standard can apply to any operating
system.
62. PCNTL == $fooDoo
POSIX functions
In PHP: POSIX functions in PHP allow you to interact
with the POSIX interface of the operating system. For
process control functions we mainly use POSIX to
retrieve PID information and terminate/kill processes
63. PCNTL == $fooDoo
POSIX functions
posix_getpid()
• Determine the PID of the current process
• pcntl_fork() returnthe child process process is the same as
posix_getpid() for
value for the parent
posix_getppid()
• Determine the PID of the parent of the process
• posix_getpid() valuethe childparent process is the same as
posix_getppid() for
for the
process
• posix_getppid() value for the parent process is the PID of the bash
session
65. PCNTL == $fooDoo
POSIX functions
posix_kill()
• Send a signal to a certain process
• posix_kill($pid,0): gets PID status
• true: pid exists
• false: pid no longer exists
• posix_kill($pid,9) or posix_kill($pid,SIGKILL): kill a process
• posix_kill($pid,15) or posix_kill($pid,SIGTERM): terminate a process
66. PCNTL == $fooDoo
POSIX functions: example of posix_kill()
<?php
$pid=pcntl_fork();
if ($pid == -1) {
die(quot;could not forkquot;);
} else if ($pid) {
$exists = posix_kill($pid,0)?'still':'no longer';
echo quot;[parent] Child process $pid $exists existsquot;.PHP_EOL;
echo quot;[parent] Killing child process $pidquot;.PHP_EOL;
posix_kill($pid,SIGKILL);
echo quot;[parent] Child process $pid killedquot;.PHP_EOL;
pcntl_wait($status);
$exists = posix_kill($pid,0)?'still':'no longer';
echo quot;[parent] Child process $pid $exists existsquot;.PHP_EOL;
} else {
while(true){
sleep(100);
}
exit;
}
67. Wake the daemons
The daemon (1)
#!/usr/bin/php
<?php
date_default_timezone_set('Europe/Brussels');
$options = getopt('p:d:');
/**
* Determine pidfile
*/
if(!array_key_exists('p',$options)){
die('No pidfile specified'.PHP_EOL);
}
define('PIDFILE',$options['p']);
/**
* Determine datadir
*/
if(!array_key_exists('d',$options)){
die('No data directory specified'.PHP_EOL);
}else{
if(!is_dir($options['d'])){
die('Data directory does not exist'.PHP_EOL);
}
}
define('DATADIR',$options['d']);
68. Wake the daemons
The daemon (2)
/**
* Delete pidfile on startup
*/
if(file_exists(PIDFILE)){
unlink(PIDFILE);
}
/**
* Delete pidfile after SIGINT or SIGTERM
*/
function signal_handler($sig){
if(file_exists(PIDFILE)){
unlink(PIDFILE);
}
}
/**
* Set signal handler
*/
pcntl_signal(SIGINT,'signal_handler');
pcntl_signal(SIGTERM,'signal_handler');
69. Wake the daemons
The daemon (3)
/*
* Setup daemon
*/
$pid=pcntl_fork();
if ($pid == -1) {
die(quot;could not forkquot;.PHP_EOL);
} else if ($pid) {
exit;
}
/**
* System settings
*/
posix_setsid();
chdir(quot;/quot;);
/**
* Store PID
*/
$handle = fopen(PIDFILE,'w+');
fwrite($handle,posix_getpid());
fclose($handle);
70. Wake the daemons
The daemon (4)
/**
* Perform daemon logic
*/
while(true){
$handle = fopen(DATADIR.'/thijs_'.date('YmdHis'),'w+');
fwrite($handle,'abc123');
fclose($handle);
sleep(10);
}
72. Wake the daemons
The startup script (2)
function stopDaemon()
{
if(file_exists(PIDFILE)){
$pid = (int)trim(file_get_contents(PIDFILE));
if(posix_kill($pid,0)){
if(posix_kill($pid,SIGKILL)){
echo 'Daemon stopped'.PHP_EOL;
} else {
echo 'Error stopping daemon'.PHP_EOL;
}
} else {
echo 'Daemon no longer active, deleting pidfile'.PHP_EOL;
}
unlink(PIDFILE);
} else {
echo 'Daemon stopped'.PHP_EOL;
}
}
73. Wake the daemons
The startup script (3)
function statusDaemon()
{
if(file_exists(PIDFILE)){
$pid = (int)trim(file_get_contents(PIDFILE));
if(posix_kill($pid,0)){
echo'Daemon is running'.PHP_EOL;
} else {
echo 'Daemon no longer active, but pidfile still exists'.PHP_EOL;
}
} else {
echo'Daemon is not running'.PHP_EOL;
}
}
74. Wake the daemons
The startup script (4)
switch ($argv[1]){
case 'start':
startDaemon();
break;
case 'stop':
stopDaemon();
break;
case 'status':
statusDaemon();
break;
case 'restart':
stopDaemon();
sleep(2);
startDaemon();
break;
default:
die(quot;tUsage: quot;.basename(__FILE__).quot; start | stop | status |
restartquot;.PHP_EOL);
}