PHP Command Line (CLI)
PHP Beyond the Web Server In simple terms: PHP is not limited to web pages. You can run PHP scripts directly in a terminal — to import data, send scheduled emails, clean up old files, run database mig
PHP Beyond the Web Server
In simple terms: PHP is not limited to web pages. You can run PHP scripts directly in a terminal — to import data, send scheduled emails, clean up old files, run database migrations, or build small tools. Frameworks rely on this constantly: php artisan migrate, php bin/console cache:clear, and composer install are all PHP command-line programs.
Technically: The CLI SAPI (Server API) is the php executable used in terminals. It differs from web SAPIs (PHP-FPM, Apache module): there are no HTTP requests or headers, output goes to the terminal, arguments arrive in $argv, the standard streams STDIN, STDOUT, and STDERR are available, max_execution_time defaults to 0 (unlimited), and it usually reads its own php.ini.
Useful php Commands
| Command | Purpose |
|---|---|
php script.php arg1 arg2 | Run a script with arguments |
php -v | Show the PHP version |
php -m | List loaded extensions |
php -i | Show the full configuration (like phpinfo() as text) |
php --ini | Show which php.ini files are loaded |
php -l file.php | Check a file for syntax errors ("lint") |
php -r 'echo PHP_VERSION, PHP_EOL;' | Run a short piece of code |
php -a | Interactive shell |
php -S localhost:8000 -t public | Start the built-in development web server (see PHP Built-in Web Server) |
php -d memory_limit=1G script.php | Override a php.ini setting for one run |
php -l src/Order.php
No syntax errors detected in src/Order.php
Running php -l on every changed file before committing (or in continuous integration) catches syntax errors before they reach a server.
Command-Line Arguments: $argv and $argc
<?php
// args.php
echo "Script: {$argv[0]}\n";
echo "Argument count: " . ($argc - 1) . "\n";
foreach (array_slice($argv, 1) as $index => $argument) {
echo " [" . ($index + 1) . "] $argument\n";
}
php args.php report.csv "Tamil Nadu" --verbose
Output
Script: args.php
Argument count: 3
[1] report.csv
[2] Tamil Nadu
[3] --verbose
$argv[0] is the script name; the arguments follow. Quotes in the shell keep Tamil Nadu together as one argument. $argv and $argc are available in the global scope of CLI scripts (inside functions, use $_SERVER['argv']).
Parsing Options With getopt()
<?php
// greet.php
$options = getopt('', ['name:', 'times::', 'shout']);
if (!isset($options['name']) || !is_string($options['name']) || $options['name'] === '') {
fwrite(STDERR, "Usage: php greet.php --name=NAME [--times=N] [--shout]\n");
exit(1);
}
$times = (int) ($options['times'] ?? 1);
$message = "Hello, {$options['name']}!";
if (isset($options['shout'])) {
$message = strtoupper($message);
}
for ($i = 0; $i < max(1, $times); $i++) {
echo $message . "\n";
}
exit(0);
php greet.php --name=Asha --times=2 --shout
php greet.php
echo $?
Output
HELLO, ASHA!
HELLO, ASHA!
Usage: php greet.php --name=NAME [--times=N] [--shout]
1
How It Works
| Definition | Meaning | Example |
|---|---|---|
name: | Required value | --name=Asha or --name Asha |
times:: | Optional value (must use =) | --times=2 |
shout | Flag without value | --shout (value false in the array) |
getopt()returns an array of the options that were given; the first argument ('') would define short options such as-n.- Error messages go to
STDERR, separate from normal output onSTDOUT, so they remain visible even when the output is redirected to a file. exit(1)returns a non-zero exit code, which signals failure to shell scripts, cron, and CI tools.$?in the shell shows the exit code of the last command;0means success.
Reading Input: STDIN
CLI scripts can process data piped from other programs or files:
<?php
// sum.php — reads one number per line
$total = 0;
$lines = 0;
while (($line = fgets(STDIN)) !== false) {
$line = trim($line);
if ($line === '') {
continue;
}
$total += (int) $line;
$lines++;
}
echo "Lines: $lines, total: $total\n";
printf "10\n20\n12\n" | php sum.php
php sum.php < numbers.txt
Output (for the first command)
Lines: 3, total: 42
Reading line by line keeps memory use constant, so the same script can sum a file with millions of lines. For interactive questions, readline('Continue? [y/N] ') reads a line typed by the user.
Detecting the CLI and Making Executable Scripts
#!/usr/bin/env php
<?php
// bin/cleanup
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit;
}
$deleted = 0;
foreach (glob(__DIR__ . '/../storage/tmp/*') ?: [] as $file) {
if (is_file($file) && filemtime($file) < time() - 86400) {
unlink($file);
$deleted++;
}
}
fwrite(STDOUT, "Deleted $deleted temporary files\n");
chmod +x bin/cleanup
./bin/cleanup
- The shebang line (
#!/usr/bin/env php) lets the operating system run the file directly; PHP skips this first line. - The
PHP_SAPIcheck ensures that a maintenance script placed in a web-accessible folder by mistake cannot be triggered through the browser. Better still, keep such scripts inbin/, outside the document root.
Scheduled Jobs With Cron
On Linux servers, cron runs CLI scripts on a schedule:
# crontab -e (minute hour day month weekday command)
15 2 * * * /usr/bin/php /var/www/shop/bin/cleanup >> /var/log/shop/cleanup.log 2>&1
*/5 * * * * /usr/bin/php /var/www/shop/bin/send-queued-emails
- Use absolute paths for PHP and the script; cron runs with a minimal environment and a different working directory.
- Redirect output to a log file (
>> file 2>&1) so errors are not lost. - Prevent overlapping runs of long jobs with a lock (for example,
flockin the shell, or a lock file in PHP). - Frameworks offer schedulers (Laravel's task scheduling, Symfony Scheduler) that need only one cron entry.
Long-Running CLI Processes
Queue workers and daemons run for hours or days. They require extra care:
| Concern | Advice |
|---|---|
| Memory leaks | Monitor memory_get_usage(); restart workers periodically (after N jobs) |
| Stale connections | Reconnect to the database after errors such as "server has gone away" |
| Static state | Reset caches and static properties between jobs |
| Supervision | Run workers under a process manager (systemd, Supervisor) that restarts them |
| Signals | Handle SIGTERM gracefully with pcntl_signal() to finish the current job before stopping |
For larger command-line applications, libraries such as Symfony Console provide argument parsing, help texts, colored output, progress bars, and testing tools.
Common Mistakes
Assuming Web Settings Apply to the CLI
Effect: Different php.ini, time zone, or extensions cause unexpected behavior.
Fix: Check with php --ini and configure the CLI explicitly.
Using Relative Paths in Scheduled Scripts
Effect: Files are not found when cron runs the script from another directory.
Fix: Build paths from __DIR__.
Printing Errors to STDOUT and Always Exiting With 0
Effect: Automation cannot detect failures.
Fix: Write errors to STDERR and use non-zero exit codes.
Leaving CLI Scripts Accessible via the Web
Effect: Anyone can trigger maintenance tasks.
Fix: Store them outside the document root and check PHP_SAPI.
Best Practices
- Use the CLI for imports, maintenance, scheduled jobs, and developer tools.
- Parse options with
getopt()(or Symfony Console) and print usage help on invalid input. - Separate output (
STDOUT) from errors (STDERR) and return meaningful exit codes. - Process input as streams to keep memory use constant.
- Schedule jobs with cron using absolute paths, logging, and locks; supervise long-running workers.
Official reference: https://www.php.net/manual/en/features.commandline.php