Выполнение PHP-сценариев по расписанию: cron-события и планировщики
Основы cron-задач для PHP
Для регулярного выполнения PHP-сценариев по расписанию в Unix-подобных системах применяется планировщик cron. Статья рассматривает основные подходы к организации cron-событий в PHP, их цели, ограничения и способы решения типичных проблем. Подробные примеры помогут настроить выполнение скриптов с минимальными усилиями.
Как наиболее эффективно запускать PHP-скрипты по расписанию?
Основное решение: использование системного cron с вызовом PHP CLI напрямую. Это самый надёжный и быстрый метод, не зависящий от веб-сервера. Настройка выполняется через crontab или файлы в /etc/cron.d/.
Пример записи в crontab для выполнения скрипта каждые 15 минут:
*/15 * * * * /usr/bin/php /home/user/cron/script.php >> /var/log/cron.log 2>&1Cron event php (cron событие php)
Пояснение шагов:
*/15 * * * *– время запуска: каждые 15 минут./usr/bin/php– полный путь к интерпретатору PHP (узнать командойwhich php)./home/user/cron/script.php– полный путь к скрипту.>> /var/log/cron.log 2>&1– перенаправление stdout и stderr в лог-файл.
Типичная ошибка: использование относительного пути или отсутствие прав на выполнение. Решение – указывать абсолютные пути и дать права chmod +x script.php при необходимости.
Ещё одна проблема – скрипт не выполняется из-за отсутствия переменных окружения (например, PATH). Рекомендуется в начале скрипта задавать нужные константы или использовать полные пути внутри кода.
Как организовать cron-задание без доступа к системе (например, на shared-хостинге)?
Используется веб-крон – внешний сервис, который по расписанию обращается к URL вашего скрипта через HTTP. Этот метод подходит для хостингов, где нет прямого доступа к crontab.
Пример записи в crontab внешнего сервера (или настройки в панели хостинга):
*/5 * * * * curl -s -o /dev/null 'https://example.com/cron.php'
Альтернатива – использовать wget или PHP-функции для инициирования запроса из другого скрипта. Важно защитить URL от несанкционированного доступа (например, токеном).
Проблема: если скрипт выполняется дольше интервала, возможны перекрытия. Решение – реализовать блокировку через файл или MySQL.
Также веб-крон может привести к уязвимости, если URL станет известен посторонним – обязательно используйте secret-ключ или IP-фильтрацию.
Как реализовать внутренний планировщик на PHP без внешнего cron?
Иногда требуется выполнять задачи в рамках того же процесса, например, для демонов или ботов. Создаётся бесконечный цикл, проверяющий время последнего запуска.
$interval = 300; // 5 минут
while (true) {
// выполнение задачи
doTask();
sleep($interval);
}
Такой подход плохо масштабируется и требует управления памятью. Более продвинутый вариант – использование библиотеки ReactPHP или Amp для асинхронных событий.
Проблема: при сбое процесса планировщик останавливается. Для надёжности используют supervisord или systemd для перезапуска.
Типичная ошибка: утечка памяти – в цикле необходимо контролировать использование ресурсов.
Как применить облачный сервис для cron-задач PHP?
Сторонние сервисы (EasyCron, Cron-Job, SetCronJob) берут на себя расписание и отправляют HTTP-запросы к вашему приложению. Это удобно, когда не нужно управлять сервером.
Пример использования API EasyCron для создания задания:
POST /add?url=https://mysite.com/cron.php&interval=10
Сервис гарантирует запуск с точностью до минуты и предоставляет логи.
Ограничение: бесплатные тарифы часто имеют лимит на количество вызовов в месяц. Также полагаться на внешний сервис – значит зависеть от его доступности.
Расширенные примеры и сценарии
1. Crontab с различными интервалами и ротацией логов
Пример crontab для еженочного и ежечасного выполнения с раздельными логами:
# Ежедневно в 2:30
30 2 * * * /usr/bin/php /opt/tasks/daily_report.php >> /var/log/daily_$(date +\%Y\%m\%d).log 2>&1
# Каждый час в 15 минут
15 * * * * /usr/bin/php /opt/tasks/hourly_check.php | tee -a /var/log/hourly.log
Результат: файлы daily_20250215.log и hourly.log сохраняют вывод. Ошибки тоже попадают в лог.
/var/log/daily_20250215.log /var/log/hourly.log
2. Предотвращение дублирования запусков (PID-файл)
Скрипт проверяет наличие PID-файла и завершается, если задача уже выполняется.
$pidFile = '/tmp/my_cron.pid';
if (file_exists($pidFile)) {
$pid = file_get_contents($pidFile);
// проверка, жив ли процесс (только Unix)
if (posix_kill((int)$pid, 0)) {
echo "Task already running (PID $pid)\n";
exit(0);
}
}
file_put_contents($pidFile, getmypid());
register_shutdown_function(function() use ($pidFile) {
unlink($pidFile);
});
// основная работа
echo "Task started at " . date('Y-m-d H:i:s') . "\n";
sleep(30);
echo "Task finished\n";
Вывод при первом запуске:
Task started at 2025-02-15 10:00:00 Task finished
При втором одновременном запуске:
Task already running (PID 12345)
3. Использование библиотеки cron-expression для гибкого расписания
Библиотека cron-expression (устанавливается через Composer) позволяет вычислять следующее время выполнения.
require 'vendor/autoload.php';
use Cron\CronExpression;
$expression = CronExpression::factory('*/10 * * * *');
echo "Next run: " . $expression->getNextRunDate()->format('Y-m-d H:i:s') . "\n";
echo "Previous run: " . $expression->getPreviousRunDate()->format('Y-m-d H:i:s') . "\n";
Результат:
Next run: 2025-02-15 10:10:00 Previous run: 2025-02-15 10:00:00
4. Cron-задача с использованием Symfony Console (Laravel / Symfony)
В современных фреймворках команды регистрируются как сервисы и запускаются через php artisan schedule:run. Пример консольной команды в Symfony:
// src/Command/CleanupCommand.php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CleanupCommand extends Command
{
protected static $defaultName = 'app:cleanup';
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('Cleaning temporary files...');
// логика очистки
return Command::SUCCESS;
}
}
Запись в crontab для вызова фреймворковского планировщика (Laravel):
* * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1
5. Docker-контейнер с внутренним cron и PHP
В образе Docker запускается cron-демон, который выполняет PHP-скрипты. Пример Dockerfile:
FROM php:8.2-cli
RUN apt-get update && apt-get install -y cron && \
echo "* * * * * root /usr/local/bin/php /var/www/html/cron.php >> /var/log/cron.log 2>&1" > /etc/cron.d/my-cron && \
chmod 0644 /etc/cron.d/my-cron && \
touch /var/log/cron.log
CMD ["cron", "-f"]
Контейнер будет запускать cron.php каждую минуту. Логи доступны внутри контейнера.
6. Отправка электронной почты из cron-скрипта с помощью PHPMailer
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require 'vendor/autoload.php';
$mail = new PHPMailer(true);
try {
$mail->isSMTP();
$mail->Host = 'smtp.example.com';
$mail->SMTPAuth = true;
$mail->Username = 'user@example.com';
$mail->Password = 'secret';
$mail->setFrom('cron@example.com', 'Cron Job');
$mail->addAddress('admin@example.com');
$mail->Subject = 'Cron report';
$mail->Body = 'Task completed at ' . date('Y-m-d H:i:s');
$mail->send();
echo 'Email sent successfully';
} catch (Exception $e) {
echo "Mailer Error: {$mail->ErrorInfo}\n";
}
Вывод:
Email sent successfully
7. Обработка больших объёмов данных в cron-задаче с разбивкой на пакеты
$batchSize = 500;
$offset = 0;
$total = 10000;
while ($offset < $total) {
// выборка $batchSize записей из БД
$records = getRecords($batchSize, $offset);
foreach ($records as $record) {
process($record);
}
$offset += $batchSize;
// предотвращение слишком долгого выполнения
if (memory_get_usage() > 100 * 1024 * 1024) break;
}
echo "Processed $offset records\n";