Chapter 23: CLI Scripting and Automation
PHP is not only for web applications. It can also serve as a powerful scripting tool for command line automation, background jobs, and system tasks. With the same syntax and runtime you use for web code, you can create command line utilities that integrate with APIs, process data, or monitor systems.
Reading Arguments and Options
When you run a PHP script from the terminal, command line arguments are made available through the $argv array. You can also use getopt() for more structured parsing of options and flags.
Accessing positional arguments
The first element of $argv is always the script name. The remaining elements are the arguments provided by the user. You can read them directly or shift them to handle sequential inputs.
<?php
// example: php greet.php Robin
if ($argc < 2) {
fwrite(STDERR, "Usage: php greet.php <name>\n");
exit(1);
}
$name = $argv[1];
echo "Hello, $name\n";
Parsing options with getopt()
The getopt() function supports short options like -v or long options like --verbose. Each option can require a value, have an optional value, or be a simple flag.
<?php
// php script.php -f data.txt --verbose
$options = getopt('f:v', ['file:', 'verbose']);
if (isset($options['file'])) {
echo "File: {$options['file']}\n";
}
if (isset($options['verbose']) || isset($options['v'])) {
echo "Verbose mode enabled\n";
}
Environment Variables and Config
Environment variables are key to portable scripts. They let you keep secrets and configuration out of code. You can read them with getenv() or from the $_ENV array, and tools like vlucas/phpdotenv can help load them from .env files.
Loading variables safely
<?php
// php dotenv example
require __DIR__ . '/vendor/autoload.php';
Dotenv\Dotenv::createImmutable(__DIR__)->load();
$dbHost = getenv('DB_HOST') ?: 'localhost';
$dbUser = $_ENV['DB_USER'] ?? 'root';
echo "Connecting to $dbHost as $dbUser\n";
Interactive Scripts
Interactive CLI scripts prompt the user for input and provide feedback. You can use readline() for typed input and fgets(STDIN) for basic prompts. Formatting output clearly helps guide the user through a task.
Prompting for input and confirmation
<?php
$name = readline("Enter your name: ");
$confirm = readline("Are you sure (y/n)? ");
if (strtolower($confirm) === 'y') {
echo "Welcome, $name!\n";
} else {
echo "Cancelled.\n";
}
Adding colors and formatting
You can use ANSI escape codes to color output in terminals that support it. This helps highlight warnings or success messages.
<?php
echo "\033[32mSuccess!\033[0m\n"; // green
echo "\033[31mError!\033[0m\n"; // red
Task Runners and Scheduling
CLI scripts can act as reusable tasks that you run periodically or chain together. You can integrate them with cron, systemd timers, or queue systems. Laravel’s Artisan and Symfony’s Console component both provide structured task management.
Using Laravel Artisan commands
// app/Console/Commands/CleanTemp.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class CleanTemp extends Command
{
protected $signature = 'cleanup:temp';
protected $description = 'Remove temporary files older than one day';
public function handle(): int
{
$count = count(glob(storage_path('temp/*')));
$this->info("Deleting $count temp files...");
return Command::SUCCESS;
}
}
Scheduling with Laravel’s scheduler
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
$schedule->command('cleanup:temp')->dailyAt('01:00');
}
Packaging CLI Tools
You can distribute standalone CLI applications by packaging them as PHAR archives or Composer global packages. This makes installation and updates straightforward for other developers or servers.
Creating a PHAR archive
PHAR (PHP Archive) bundles your code and dependencies into a single executable file. You can create it manually or with box or phar-composer.
<?php
$phar = new Phar('tool.phar');
$phar->buildFromDirectory(__DIR__ . '/src');
$phar->setStub($phar->createDefaultStub('main.php'));
Installing with Composer globally
To share a command via Composer, define a bin entry in your composer.json. When installed globally, Composer will add a symlink in the user’s PATH.
{
"name": "example/tool",
"bin": ["bin/tool"]
}
Long-running Workers
Long-running workers handle queues, background processing, and asynchronous tasks. PHP can maintain a process loop that polls for jobs or listens for messages, but you must watch memory leaks and handle signals properly to ensure stability.
Implementing a resilient job loop
<?php
declare(strict_types=1);
while (true) {
$job = getNextJob(); // fetch from queue …
if ($job) {
try {
handleJob($job);
} catch (Throwable $e) {
error_log('Job failed: ' . $e->getMessage());
}
}
sleep(5); // adjust polling delay
}
Using system signals for graceful shutdown
Catch system signals like SIGTERM and SIGINT to stop workers cleanly during deployments or restarts. This prevents half-finished work and file corruption.
<?php
declare(ticks = 1);
$running = true;
pcntl_signal(SIGTERM, fn() => $GLOBALS['running'] = false);
pcntl_signal(SIGINT, fn() => $GLOBALS['running'] = false);
while ($running) {
doWork();
sleep(2);
}
echo "Worker stopped gracefully.\n";
systemd or supervisord with health checks for reliable uptime.