PHP — это не только про веб
Когда говорят «PHP», большинство сразу представляют себе ленту новостей ВКонтакте, корзину в интернет-магазине или бэкенд WordPress. Это логично: язык рождался как инструмент для веба. Но что, если я скажу вам, что PHP отлично справляется с задачами, которые обычно отдают Python, Bash или Go? Речь о кроссплатформенной разработке на PHP — создании утилит, которые работают и на Windows, и на Linux, и на macOS без переписывания кода.
В этой статье мы не будем учить вас верстать формы. Мы покажем, как написать консольный файловый менеджер, парсер логов и систему мониторинга папок — и всё это на чистом PHP, без единого веб-сервера. Вы узнаете, как обходить подводные камни разных ОС и писать код, который запускается везде одинаково.
Готовы? Тогда открывайте терминал и приготовьтесь удивляться возможностям старого доброго PHP.
Почему PHP для кроссплатформенных задач? Три железных аргумента
Прежде чем нырять в код, давайте разберёмся, зачем вообще использовать PHP для desktop- и server-утилит. У Python для этого есть Django и Flask, у Node.js — Express. Зачем нам PHP?
- Повсеместная доступность. PHP установлен почти на каждом shared-хостинге и на 80% серверов в мире. Если вы пишете инструмент для администрирования, велика вероятность, что на целевой машине уже есть PHP. Не нужно ставить дополнительный рантайм.
- Низкий порог входа для веб-разработчиков. Вы уже знаете синтаксис, функции работы со строками и массивами. Вам не нужно учить новый язык, чтобы автоматизировать рутину на сервере.
- Богатая стандартная библиотека. В PHP есть всё для работы с файлами, процессами, сетевыми сокетами и zip-архивами. А PECL-расширения вроде
parallelилиpthreadsдобавляют многопоточность.
Конечно, PHP не идеален для GUI-приложений (хотя есть PHP-GTK). Но для консольных утилит и демонов — это зрелый и стабильный инструмент. Давайте посмотрим, как писать такой код правильно.
Основы кроссплатформенного PHP-кода
Первое, что нужно запомнить: разделители путей. На Linux это /, на Windows — \. PHP предоставляет константу DIRECTORY_SEPARATOR, но ещё удобнее использовать функцию DIRECTORY_SEPARATOR или универсальный слеш / — Windows его прекрасно понимает с PHP 7+.
Правильная работа с путями
<?php
// Плохо — жёстко зашитый слеш
$path = '/var/log/errors.log'; // Упадёт на Windows
// Хорошо — используем DIRECTORY_SEPARATOR
$path = 'var' . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'errors.log';
// Отлично — используем / и realpath()
$path = __DIR__ . '/logs/errors.log';
echo $path; // Работает на обеих ОС
?>
Второй важный момент — кодировка. На Windows по умолчанию консоль часто работает в CP866, а PHP скрипты — в UTF-8. Для вывода кириллицы в Windows используйте функцию mb_convert_encoding() или установите кодировку через chcp 65001 в начале скрипта.
Пример: кроссплатформенное чтение файла
<?php
function readLogFile(string $filename): string {
// Проверяем существование файла
if (!file_exists($filename)) {
throw new RuntimeException("Файл $filename не найден");
}
// Читаем содержимое
$content = file_get_contents($filename);
// Определяем ОС и конвертируем кодировку для вывода
if (PHP_OS_FAMILY === 'Windows') {
$content = mb_convert_encoding($content, 'CP866', 'UTF-8');
}
return $content;
}
echo readLogFile('logs/app.log');
?>
Создаём реальную утилиту: мониторинг изменений в папке
Давайте напишем полезную вещь — скрипт, который следит за папкой и выводит список новых и изменённых файлов. Это классическая задача для кроссплатформенной разработки на PHP, которая пригодится для деплоя, бэкапов или синхронизации.
Шаг 1. Получаем список файлов
<?php
function scanDirectory(string $dir): array {
$files = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $fileinfo) {
if ($fileinfo->isFile()) {
$files[$fileinfo->getPathname()] = $fileinfo->getMTime();
}
}
return $files;
}
?>
Этот код работает на любой ОС. RecursiveDirectoryIterator правильно обрабатывает и слеши, и обратные слеши.
Шаг 2. Сравниваем с предыдущим состоянием
<?php
function detectChanges(string $dir, string $stateFile = 'state.json'): array {
$current = scanDirectory($dir);
$previous = [];
// Загружаем предыдущее состояние
if (file_exists($stateFile)) {
$previous = json_decode(file_get_contents($stateFile), true) ?? [];
}
$newFiles = [];
$modifiedFiles = [];
$deletedFiles = [];
// Ищем новые и изменённые
foreach ($current as $path => $mtime) {
if (!isset($previous[$path])) {
$newFiles[] = $path;
} elseif ($previous[$path] !== $mtime) {
$modifiedFiles[] = $path;
}
}
// Ищем удалённые
foreach ($previous as $path => $mtime) {
if (!isset($current[$path])) {
$deletedFiles[] = $path;
}
}
// Сохраняем текущее состояние
file_put_contents($stateFile, json_encode($current, JSON_PRETTY_PRINT));
return [
'new' => $newFiles,
'modified' => $modifiedFiles,
'deleted' => $deletedFiles
];
}
?>
Шаг 3. Запускаем в цикле с задержкой
<?php
// Устанавливаем кодировку консоли для Windows
if (PHP_OS_FAMILY === 'Windows') {
system('chcp 65001 > nul');
}
echo "Мониторинг папки 'data/' запущен. Нажмите Ctrl+C для выхода.\n";
while (true) {
$changes = detectChanges('data/');
if (!empty($changes['new'])) {
echo "Новые файлы:\n";
foreach ($changes['new'] as $file) {
echo " + " . $file . "\n";
}
}
if (!empty($