PHP CLI ile Komut Satırında Script Çalıştırma
PHP denince aklınıza ilk gelen şey muhtemelen web sunucusunda çalışan, tarayıcıya HTML döken bir dil. Ama PHP’nin bir de karanlık tarafı var: komut satırı. PHP CLI (Command Line Interface), aslında birçok sysadmin’in hayatını kolaylaştırabilecek güçlü bir araç. Cron job’lardan sistem otomasyonuna, veritabanı migrasyonlarından log analizine kadar PHP CLI’yi kullanabileceğiniz onlarca senaryo mevcut. Bu yazıda PHP CLI’yi A’dan Z’ye ele alacağız.
PHP CLI Nedir ve Web PHP’den Farkı Ne?
PHP’nin iki farklı çalışma modu var. Birincisi, Apache veya Nginx arkasında çalışan PHP-FPM veya mod_php. İkincisi ise doğrudan terminalden çalıştırdığınız PHP CLI binary’si.
Bu iki mod arasındaki farklar sandığınızdan daha derin:
- Zaman aşımı: Web PHP’de
max_execution_timegenellikle 30-60 saniye. CLI’de ise varsayılan olarak 0, yani sınırsız. - Bellek limiti: Web PHP genellikle 128-256MB ile sınırlıyken, CLI’de çok daha fazlasını kullanabilirsiniz.
- HTTP başlıkları: CLI’de
header()fonksiyonu işe yaramaz, kimseye gönderecek tarayıcı yok. - $_SERVER değişkenleri: Web’de dolup taşan
$_SERVER, CLI’de çok daha sade. - php.ini farklılığı: CLI genellikle farklı bir php.ini dosyası kullanır.
Hangi php.ini’nin kullanıldığını görmek için:
php --ini
# Veya daha detaylı:
php -r "phpinfo();" | grep "Loaded Configuration"
PHP CLI Kurulumu ve Versiyon Yönetimi
Birçok Linux dağıtımında PHP CLI zaten yüklü gelir ama özellikle Ubuntu/Debian sistemlerde bazen sadece web modülü kurulu olur.
# Ubuntu/Debian
sudo apt update
sudo apt install php-cli
# RHEL/CentOS/Rocky Linux
sudo dnf install php-cli
# Birden fazla PHP versiyonu için (Ubuntu)
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php8.1-cli php8.2-cli php8.3-cli
Hangi PHP’nin aktif olduğunu kontrol etmek:
php --version
which php
ls -la /usr/bin/php*
Eğer sisteminizde birden fazla PHP versiyonu varsa ve hangisinin kullanılacağını belirlemek istiyorsanız:
# Ubuntu'da update-alternatives kullanımı
sudo update-alternatives --config php
# Veya doğrudan versiyon belirterek çalıştırma
php8.1 script.php
php8.2 script.php
/usr/bin/php8.3 script.php
İlk PHP CLI Script’iniz
Klasik Hello World’den daha işlevsel bir şeyle başlayalım. Aşağıdaki script, sunucunun temel sistem bilgilerini toplar:
cat << 'EOF' > /usr/local/bin/syscheck.php
#!/usr/bin/env php
<?php
$hostname = gethostname();
$phpVersion = phpversion();
$memTotal = shell_exec("grep MemTotal /proc/meminfo | awk '{print $2}'");
$loadAvg = sys_getloadavg();
echo "=== Sistem Bilgisi ===" . PHP_EOL;
echo "Hostname: " . $hostname . PHP_EOL;
echo "PHP Versiyon: " . $phpVersion . PHP_EOL;
echo "Toplam RAM: " . round(trim($memTotal) / 1024) . " MB" . PHP_EOL;
echo "Load Average: " . implode(", ", array_map(fn($v) => round($v, 2), $loadAvg)) . PHP_EOL;
echo "Tarih/Saat: " . date("Y-m-d H:i:s") . PHP_EOL;
EOF
chmod +x /usr/local/bin/syscheck.php
php /usr/local/bin/syscheck.php
Dikkat edin, ilk satırdaki #!/usr/bin/env php shebang satırı. Bu sayede script’i doğrudan ./syscheck.php şeklinde de çalıştırabilirsiniz.
Komut Satırı Argümanlarını İşlemek
PHP CLI’nin en güçlü özelliklerinden biri argüman işleme. $argv ve $argc değişkenleri ile birlikte getopt() fonksiyonu size tam kontrol sağlar.
cat << 'EOF' > backup_check.php
#!/usr/bin/env php
<?php
// Argüman yoksa kullanım bilgisi göster
if ($argc < 2) {
echo "Kullanım: php backup_check.php [--path=/backup/dir] [--days=7] [--verbose]" . PHP_EOL;
exit(1);
}
// Uzun ve kısa opsiyonları tanımla
$options = getopt("vp:d:", ["verbose", "path:", "days:"]);
$verbose = isset($options['v']) || isset($options['verbose']);
$path = $options['p'] ?? $options['path'] ?? '/var/backups';
$days = (int)($options['d'] ?? $options['days'] ?? 7);
if ($verbose) {
echo "Kontrol edilen dizin: $path" . PHP_EOL;
echo "Kontrol süresi: Son $days gün" . PHP_EOL;
}
if (!is_dir($path)) {
fwrite(STDERR, "Hata: $path dizini bulunamadı!" . PHP_EOL);
exit(2);
}
$cutoffTime = time() - ($days * 86400);
$files = glob($path . '/*.{tar,gz,zip,sql}', GLOB_BRACE);
$recentBackups = array_filter($files, fn($f) => filemtime($f) > $cutoffTime);
echo "Son $days günde " . count($recentBackups) . " backup dosyası bulundu." . PHP_EOL;
if (count($recentBackups) === 0) {
fwrite(STDERR, "UYARI: Hiç güncel backup yok!" . PHP_EOL);
exit(3);
}
EOF
Bu script’i şu şekilde kullanabilirsiniz:
php backup_check.php --path=/mnt/backups --days=3 --verbose
php backup_check.php -p /mnt/backups -d 3 -v
STDIN, STDOUT ve STDERR Kullanımı
CLI script’lerinde standart giriş/çıkış akışlarını doğru kullanmak çok önemli. Özellikle pipe ile başka komutlarla entegrasyon yapıyorsanız bu kritik.
cat << 'EOF' > log_parser.php
#!/usr/bin/env php
<?php
// STDIN'den okuma - pipe ile kullanım için
$errorCount = 0;
$warningCount = 0;
$criticalLines = [];
$handle = fopen("php://stdin", "r");
while (($line = fgets($handle)) !== false) {
$line = trim($line);
if (stripos($line, 'ERROR') !== false) {
$errorCount++;
// STDOUT'a yaz
echo "[HATA BULUNDU] $line" . PHP_EOL;
} elseif (stripos($line, 'WARNING') !== false) {
$warningCount++;
} elseif (stripos($line, 'CRITICAL') !== false) {
$criticalLines[] = $line;
// STDERR'e yaz - bu önemli!
fwrite(STDERR, "[KRİTİK] $line" . PHP_EOL);
}
}
fclose($handle);
// Özet raporu STDOUT'a
echo PHP_EOL . "=== Özet ===" . PHP_EOL;
echo "Toplam Hata: $errorCount" . PHP_EOL;
echo "Toplam Uyarı: $warningCount" . PHP_EOL;
echo "Kritik Satır: " . count($criticalLines) . PHP_EOL;
// Exit code ile sonuç bildir
exit(count($criticalLines) > 0 ? 1 : 0);
EOF
# Kullanım:
tail -n 1000 /var/log/nginx/error.log | php log_parser.php
# Veya dosyadan:
php log_parser.php < /var/log/nginx/error.log
Gerçek Dünya Senaryosu: Veritabanı Temizleme Script’i
İşte tam anlamıyla işe yarar bir script. MySQL veritabanındaki eski kayıtları temizleyen, transaction kullanan ve her şeyi loglayan bir örnek:
cat << 'EOF' > /etc/cron.daily/db_cleanup.php
#!/usr/bin/env php
<?php
define('LOG_FILE', '/var/log/db_cleanup.log');
define('BATCH_SIZE', 1000);
function writeLog(string $message, string $level = 'INFO'): void {
$timestamp = date('Y-m-d H:i:s');
$logLine = "[$timestamp] [$level] $message" . PHP_EOL;
file_put_contents(LOG_FILE, $logLine, FILE_APPEND);
if ($level === 'ERROR') {
fwrite(STDERR, $logLine);
} else {
echo $logLine;
}
}
// Ortam değişkenlerinden veya config dosyasından al
$config = [
'host' => getenv('DB_HOST') ?: 'localhost',
'dbname' => getenv('DB_NAME') ?: 'production',
'user' => getenv('DB_USER') ?: 'cleaner',
'pass' => getenv('DB_PASS') ?: '',
];
try {
$dsn = "mysql:host={$config['host']};dbname={$config['dbname']};charset=utf8mb4";
$pdo = new PDO($dsn, $config['user'], $config['pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
writeLog("Veritabanı bağlantısı kuruldu");
$totalDeleted = 0;
$cutoffDate = date('Y-m-d', strtotime('-90 days'));
// Batch halinde sil - büyük tablolarda lock sorununu önle
do {
$pdo->beginTransaction();
$stmt = $pdo->prepare("
DELETE FROM session_logs
WHERE created_at < :cutoff
LIMIT :batch_size
");
$stmt->bindValue(':cutoff', $cutoffDate);
$stmt->bindValue(':batch_size', BATCH_SIZE, PDO::PARAM_INT);
$stmt->execute();
$deleted = $stmt->rowCount();
$totalDeleted += $deleted;
$pdo->commit();
if ($deleted > 0) {
writeLog("$deleted kayıt silindi (toplam: $totalDeleted)");
sleep(1); // Sunucuya nefes aldır
}
} while ($deleted === BATCH_SIZE);
writeLog("Temizlik tamamlandı. Toplam silinen: $totalDeleted kayıt");
exit(0);
} catch (PDOException $e) {
writeLog("Veritabanı hatası: " . $e->getMessage(), 'ERROR');
exit(1);
} catch (Exception $e) {
writeLog("Genel hata: " . $e->getMessage(), 'ERROR');
exit(1);
}
EOF
chmod +x /etc/cron.daily/db_cleanup.php
Cron Job’larla Entegrasyon
PHP CLI script’lerini cron ile kullanırken dikkat etmeniz gereken bazı noktalar var. En büyük sorun genellikle PATH ve ortam değişkenleri.
# Crontab düzenle
crontab -e
Crontab içeriği şöyle olmalı:
# PHP'nin tam yolunu kullan, 'php' yazmak yetmeyebilir
# Her gece 2:30'da çalıştır, çıktıyı logla
30 2 * * * /usr/bin/php8.2 /var/scripts/db_cleanup.php >> /var/log/cleanup_cron.log 2>&1
# Ortam değişkeni gerekiyorsa
30 3 * * * DB_HOST=localhost DB_NAME=prod /usr/bin/php /var/scripts/db_cleanup.php
# Lock dosyası ile çakışmayı önle
*/5 * * * * flock -n /tmp/my_script.lock /usr/bin/php /var/scripts/worker.php
# Script başarısız olursa e-posta gönder
[email protected]
0 4 * * * /usr/bin/php /var/scripts/report.php || echo "Script başarısız oldu!"
Exit code’ların önemi burada ortaya çıkıyor. Cron, script’inizin başarılı olup olmadığını exit code’a bakarak anlıyor. exit(0) başarı, exit(1) ve üzeri hata demek.
Interactive CLI ve Kullanıcı Girişi
Bazen script’inizin kullanıcıdan girdi alması gerekir. Örneğin şifre sorması ya da onay beklemesi:
cat << 'EOF' > interactive_deploy.php
#!/usr/bin/env php
<?php
function prompt(string $question, bool $required = true): string {
echo $question . " ";
$input = trim(fgets(STDIN));
if ($required && empty($input)) {
echo "Bu alan boş bırakılamaz!" . PHP_EOL;
return prompt($question, $required);
}
return $input;
}
function confirm(string $question): bool {
echo $question . " [e/h]: ";
$input = strtolower(trim(fgets(STDIN)));
return in_array($input, ['e', 'evet', 'y', 'yes']);
}
function readPassword(string $prompt): string {
echo $prompt . " ";
// Terminal echo'yu kapat
system('stty -echo');
$password = trim(fgets(STDIN));
system('stty echo');
echo PHP_EOL;
return $password;
}
// Kullanım
$env = prompt("Deploy ortamı (staging/production):");
$branch = prompt("Git branch:", true);
if ($env === 'production') {
$password = readPassword("Production şifresi:");
// Şifre doğrulama mantığı...
}
if (!confirm("$env ortamına $branch branch'ini deploy etmek istiyor musunuz?")) {
echo "Deploy iptal edildi." . PHP_EOL;
exit(0);
}
echo "Deploy başlıyor..." . PHP_EOL;
// Deploy mantığı buraya...
EOF
PHP CLI’de Performans ve Bellek Yönetimi
Uzun süre çalışan script’lerde bellek sızıntısı gerçek bir sorun. Web PHP’de her istek sonrası bellek temizlenirken, CLI’de script sonlanana kadar her şey bellekte tutulur.
cat << 'EOF' > memory_safe_processor.php
#!/usr/bin/env php
<?php
// Başlangıç bellek kullanımını göster
echo "Başlangıç belleği: " . round(memory_get_usage() / 1024 / 1024, 2) . " MB" . PHP_EOL;
// Büyük CSV dosyasını satır satır işle (tamamını belleğe alma!)
$csvFile = $argv[1] ?? '/tmp/large_data.csv';
if (!file_exists($csvFile)) {
fwrite(STDERR, "Dosya bulunamadı: $csvFile" . PHP_EOL);
exit(1);
}
$handle = fopen($csvFile, 'r');
$processedCount = 0;
$headers = fgetcsv($handle); // İlk satır header
while (($row = fgetcsv($handle)) !== false) {
$data = array_combine($headers, $row);
// İşlemi yap
processRow($data);
$processedCount++;
// Her 1000 satırda bir durum raporu
if ($processedCount % 1000 === 0) {
$memUsage = round(memory_get_usage() / 1024 / 1024, 2);
echo "rİşlenen: $processedCount | Bellek: {$memUsage} MB";
// Bellek çok fazla şişiyorsa uyar
if (memory_get_usage() > 200 * 1024 * 1024) {
fwrite(STDERR, "Bellek limiti yaklaşıyor!" . PHP_EOL);
}
}
}
fclose($handle);
echo PHP_EOL . "Tamamlandı. Toplam: $processedCount satır" . PHP_EOL;
echo "Peak bellek: " . round(memory_get_peak_usage() / 1024 / 1024, 2) . " MB" . PHP_EOL;
function processRow(array $data): void {
// İşlem mantığı
// Önemli: fonksiyon scope'u bitince lokal değişkenler temizlenir
}
EOF
Faydalı PHP CLI Parametreleri
Günlük kullanımda işinize yarayacak parametreler:
- -r “kod”: Tek satır PHP kodu çalıştırır
- -a: Interactive mod (REPL), satır satır PHP girebilirsiniz
- -l dosya.php: Syntax kontrolü yapar, çalıştırmaz
- -f dosya.php: Dosyayı çalıştırır (-f opsiyoneldir, sadece
php dosya.phpde çalışır) - -d ayar=deger: php.ini ayarını geçersiz kılar
- -e: Extended bilgi üretir (Xdebug için)
- -n: php.ini dosyasını kullanmadan çalıştırır
- -S host:port: Dahili web sunucu başlatır (geliştirme için)
- –ri uzanti: Uzantı bilgisini gösterir
Pratik örnekler:
# Hızlı hesaplama
php -r "echo round(1024 * 1024 * 1024 / 1000000, 2) . ' MB';"
# Syntax kontrolü (deploy öncesi)
find /var/www/html -name "*.php" -exec php -l {} ; 2>&1 | grep -v "No syntax errors"
# Memory limitini geçici artır
php -d memory_limit=512M /var/scripts/heavy_process.php
# Xdebug ile çalıştır
php -d xdebug.mode=debug -d xdebug.start_with_request=yes script.php
# php.ini olmadan çalıştır (temiz ortam)
php -n -r "var_dump(ini_get('error_reporting'));"
Script’leri Servis Olarak Çalıştırmak
Uzun süre çalışan PHP CLI script’lerini (queue worker, daemon gibi) doğrudan cron’a değil, systemd’ye bırakmak çok daha doğru bir yaklaşım.
# /etc/systemd/system/php-worker.service oluştur
cat << 'EOF' > /etc/systemd/system/php-worker.service
[Unit]
Description=PHP Queue Worker
After=network.target mysql.service
[Service]
Type=simple
User=www-data
ExecStart=/usr/bin/php /var/scripts/queue_worker.php
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
Environment="DB_HOST=localhost"
Environment="DB_NAME=production"
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable php-worker
systemctl start php-worker
systemctl status php-worker
journalctl -u php-worker -f
Sonuç
PHP CLI, web geliştiricilerin genellikle görmezden geldiği ama sysadmin’lerin sevebileceği bir araç. Özellikle ekibinizde zaten PHP bilen geliştiriciler varsa, otomasyon script’lerini Bash yerine PHP ile yazmak ciddi bir avantaj sağlayabilir. Exception handling, OOP, PDO gibi modern araçlara erişim, tip güvenliği ve test edilebilirlik gibi konularda Bash’e göre çok daha güçlü.
Tabii PHP CLI’nin dezavantajları da yok değil. Bash kadar evrensel değil, her sunucuda PHP kurulu olmayabilir ve başlangıç maliyeti Bash’e göre daha yüksek. Ama veritabanı işlemleri, JSON/XML parsing, karmaşık iş mantığı gerektiren script’lerde PHP CLI rakipsiz.
En önemli ipuçları:
- Script’lerinizde her zaman anlamlı exit code’lar kullanın
- STDERR’i hata mesajları için, STDOUT’u normal çıktı için ayırın
- Büyük veri setlerini satır satır işleyin, belleğe toptan yüklemeyin
- Cron job’larında PHP’nin tam yolunu belirtin
- Uzun çalışan servisler için systemd kullanın
- Şifre gibi hassas bilgileri argüman olarak değil, ortam değişkeni olarak geçirin
PHP CLI’yi bir kez iş akışınıza dahil ettiğinizde, “bunu nasıl Bash ile yapardım” diye düşündüğünüz pek çok senaryoda kendinizi PHP yazarken bulacaksınız.
