Когда PHP выходит за пределы веба
Представьте себе ситуацию: вам нужно написать демон, который будет мониторить системные логи, парсить их и отправлять уведомления в Telegram. Или, может быть, требуется создать собственный планировщик задач, который не зависит от cron. А что если нужно написать свой простой веб-сервер или прокси? Большинство разработчиков тут же потянутся к Python, Go или C. Но что, если я скажу вам, что PHP тоже умеет в системное программирование? И умеет вполне достойно.
PHP — это не просто язык для "сайтов на WordPress". Его CLI-версия (Command Line Interface) — это полноценный инструмент для системного администрирования, работы с процессами, сетевыми сокетами и даже демонизации. В этой статье мы разберем, как использовать PHP для системного программирования, и покажем реальные примеры кода, которые работают в Linux.
Подготовка: что нужно знать
Для начала убедитесь, что у вас установлен PHP с поддержкой CLI и расширениями pcntl (управление процессами), posix (системные вызовы) и sockets (работа с сокетами). В большинстве дистрибутивов Linux это делается одной командой:
sudo apt install php-cli php-pcntl php-posix php-sockets
Проверить доступность расширений можно через php -m | grep -E 'pcntl|posix|sockets'. Если всё на месте — поехали.
Работа с процессами: fork и exec
PHP позволяет создавать дочерние процессы с помощью функции pcntl_fork(). Это мощный механизм для параллельного выполнения задач. Рассмотрим пример, который запускает три дочерних процесса, каждый из которых выполняет свою работу.
<?php
$workers = 3;
$pids = [];
for ($i = 0; $i < $workers; $i++) {
$pid = pcntl_fork();
if ($pid == -1) {
die("Не удалось создать процесс");
} elseif ($pid) {
// Родительский процесс: сохраняем PID дочернего
$pids[] = $pid;
echo "Создан дочерний процесс PID: $pid\n";
} else {
// Дочерний процесс
echo "Дочерний процесс " . getmypid() . " начал работу\n";
sleep(rand(2, 5)); // Имитация работы
echo "Дочерний процесс " . getmypid() . " завершился\n";
exit(0);
}
}
// Родитель ждет завершения всех дочерних процессов
foreach ($pids as $pid) {
pcntl_waitpid($pid, $status);
echo "Процесс $pid завершился со статусом $status\n";
}
?>
Этот код демонстрирует базовую многозадачность в PHP. Вы можете запускать параллельные задачи — парсинг логов, обработку файлов, выполнение сетевых запросов. В системном программировании такой подход незаменим, когда нужно обрабатывать большое количество данных без блокировки основного потока.
Исполнение внешних команд
Функция exec() и её вариации (shell_exec(), system()) позволяют запускать любые системные команды. Но для настоящего системного программирования лучше использовать proc_open() — она даёт полный контроль над вводом/выводом и PID процесса.
<?php
$descriptorspec = [
0 => ["pipe", "r"], // stdin
1 => ["pipe", "w"], // stdout
2 => ["pipe", "w"] // stderr
];
$process = proc_open('ping -c 4 8.8.8.8', $descriptorspec, $pipes);
if (is_resource($process)) {
// Читаем вывод
$output = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
echo "Результат ping:\n$output";
}
?>
Такой подход позволяет интегрировать PHP с любыми системными утилитами — от ps до ffmpeg.
Обработка сигналов: делаем демона вежливым
Любой серьёзный демон должен корректно обрабатывать сигналы операционной системы. PHP через расширение pcntl позволяет перехватывать сигналы (SIGTERM, SIGHUP, SIGUSR1 и другие). Это ключевой навык для системного программирования на PHP.
<?php
// Объявляем обработчик сигнала
pcntl_signal(SIGTERM, function ($signo) {
echo "Получен сигнал $signo, завершаем работу...\n";
// Здесь можно сохранить состояние, закрыть файлы и т.д.
exit(0);
});
// Устанавливаем обработчик для SIGINT (Ctrl+C)
pcntl_signal(SIGINT, function ($signo) {
echo "Получен сигнал $signo (Ctrl+C), завершаем...\n";
exit(0);
});
echo "Демон запущен. PID: " . getmypid() . ". Жду сигналы...\n";
// Бесконечный цикл с проверкой сигналов
while (true) {
pcntl_signal_dispatch();
echo "Работаю...\n";
sleep(2);
}
?>
Запустите этот скрипт в фоне (php script.php &), а затем отправьте ему сигнал kill -TERM PID. Обработчик сработает, и вы увидите сообщение о завершении. Без такой обработки демон просто "убивается" без возможности корректно закрыть файлы или соединения.
Создание полноценного демона
Теперь соберём всё вместе: напишем демона, который отсоединяется от терминала, пишет лог и обрабатывает сигналы. Это уже не игрушка, а реальный инструмент для системного программирования.
<?php
// Шаг 1: Форкаем процесс
$pid = pcntl_fork();
if ($pid == -1) {
die("Не удалось создать процесс");
} elseif ($pid) {
// Родительский процесс завершается
exit(0);
}
// Шаг 2: Становимся лидером сессии
posix_setsid();
// Шаг 3: Закрываем стандартные дескрипторы
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
// Перенаправляем вывод в лог-файл
$log = fopen('/var/log/php_daemon.log', 'a');
if (!$log) {
// Если не можем открыть лог, пишем в syslog
openlog('php_daemon', LOG_PID | LOG_PERROR, LOG_DAEMON);
syslog(LOG_INFO, 'Демон запущен');
closelog();
} else {
fwrite($log, "Демон запущен. PID: " . getmypid() . "\n");
}
// Шаг 4: Обработчики сигналов
pcntl_signal(SIGTERM, function () use ($log) {
if ($log) {
fwrite($log, "Получен SIGTERM, завершаюсь\n");
fclose($log);
}
exit(0);
});
// Шаг 5: Основной цикл работы
while (true) {
pcntl_signal_dispatch();
// Тут может быть ваша полезная работа
// Например, проверка директории, парсинг логов, отправка данных
if ($log) {
fwrite($log, "Работаю...\n");
fflush($log);
}
sleep(