[peppeocchi/php-cron-scheduler]没有 Laravel 也能优雅管理定时任务

2026-05-27 奥古斯宏 #PHP #Cron #定时任务 #任务调度 #Laravel
维护 crontab 就像维护配置文件一样痛苦,任务多了根本搞不清谁是谁。这个库让你用 PHP 代码管理所有定时任务,一条 crontab 搞定一切,230 万次安装验证的成熟方案。

用过 Laravel 的人都知道它的任务调度有多优雅 -- 不用碰 crontab,一行代码搞定定时任务。但不是每个项目都用 Laravel,很多老项目、微服务、独立脚本还是靠手动维护 crontab,任务一多就成了灾难。php-cron-scheduler 把 Laravel 的任务调度思路提炼成了一个独立包,任何 PHP 项目都能用。

php-cron-scheduler GitHub 仓库

安装

composer require peppeocchi/php-cron-scheduler

整个库只有一个外部依赖:dragonmantank/cron-expression,用来解析 cron 表达式。核心代码就 3 个类,干净利落。

Packagist 页面,累计安装 258 万次

系统配置

只需要在 crontab 里加一条记录:

* * * * * /usr/bin/php /path/to/your/scheduler.php >> /dev/null 2>&1

这一行就够了。之后所有定时任务都在 PHP 代码里管理,再也不用碰 crontab 文件。

三种任务类型

php-cron-scheduler 支持三种方式注册任务:

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

执行 PHP 脚本

最常用的方式。独立进程执行,不会阻塞主调度器:

$scheduler = new \GO\Scheduler();

// 基本用法
$scheduler->php('/path/to/cleanup.php');

// 指定 PHP 解释器路径和参数
$scheduler->php(
    '/path/to/report.php',
    '/usr/bin/php8.2',
    ['-c' => '/path/to/php.ini'],
    'daily-report'  // 任务 ID,用于标识
);

执行 Shell 命令

直接跑系统命令:

// 数据库备份
$scheduler->raw('mysqldump -u root mydb > /backup/db.sql', [], 'db-backup');

// 清理日志
$scheduler->raw('find /var/log/app -name "*.log" -mtime +30 -delete');

执行闭包函数

在当前进程中直接执行,适合轻量级操作:

$scheduler->call(function () {
    $pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '');
    $pdo->exec("DELETE FROM sessions WHERE last_activity < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 7 DAY))");
})->hourly();

闭包方式注意几点:它在当前进程中执行,不是后台任务,如果执行时间太长会阻塞后续任务。

时间调度

注册完任务后,用链式调用设置执行时间:

Cron 表达式

支持标准五段式 cron 表达式:

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

// 每天凌晨 2:30 执行
$scheduler->php('backup.php')->at('30 2 * * *');

// 每周一上午 9:00
$scheduler->php('weekly-report.php')->at('0 9 * * 1');

// 每月 1 号凌晨 3:00
$scheduler->php('monthly-report.php')->at('0 3 1 * *');

快捷方法

记不住 cron 表达式没关系,库提供了一堆语义化的方法:

// 按分钟
$scheduler->php('health-check.php')->everyMinute();
$scheduler->php('queue-worker.php')->everyMinute(5);  // 每 5 分钟

// 按小时
$scheduler->php('cache-warm.php')->hourly();       // 每小时整点
$scheduler->php('sync-data.php')->hourly(30);      // 每小时的第 30 分钟

// 按天
$scheduler->php('daily-job.php')->daily();          // 每天 00:00
$scheduler->php('evening-report.php')->daily(18, 30);  // 每天 18:30
$scheduler->php('nightly.php')->daily('23:00');     // 每天 23:00

// 按星期
$scheduler->php('monday-task.php')->monday();
$scheduler->php('friday-report.php')->friday(18);
$scheduler->php('weekend-job.php')->sunday(12, 30);

// 按月份
$scheduler->php('monthly-invoice.php')->january(1);
$scheduler->php('yearly-review.php')->december(25, 20, 30);

这些方法可以链式调用,代码可读性比 crontab 好太多。

指定日期

还能在特定日期执行一次性任务:

$scheduler->php('launch.php')->date('2026-06-01 00:00');
$scheduler->php('event.php')->date(new DateTime('2026-12-25'));

防止重叠执行

定时任务最怕的就是上一次还没跑完,下一次又启动了。onlyOne() 方法用文件锁解决这个问题:

$scheduler->php('heavy-import.php')
    ->everyMinute(5)
    ->onlyOne();

// 自定义锁文件目录
$scheduler->php('heavy-import.php')
    ->everyMinute(5)
    ->onlyOne('/tmp/my-locks');

// 重叠时执行自定义逻辑
$scheduler->php('heavy-import.php')
    ->everyMinute(5)
    ->onlyOne(null, function ($lockFileTime) {
        // 如果锁文件超过 1 小时,说明上次任务可能卡死了,允许执行
        return (time() - $lockFileTime) > 3600;
    });

原理很简单:任务开始时创建 .lock 文件,结束后删除。如果发现锁文件存在,就跳过本次执行。

条件执行

有些任务只在特定条件下才需要跑,用 when() 添加条件判断:

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

// 仅工作日执行
$scheduler->php('workday-report.php')
    ->daily(9)
    ->when(function () {
        return date('N') < 6;  // 1-5 是工作日
    });

// 仅生产环境执行
$scheduler->php('production-sync.php')
    ->hourly()
    ->when(function () {
        return getenv('APP_ENV') === 'production';
    });

// 仅当数据库有新数据时执行
$scheduler->call(function () {
    return processPendingOrders();
})->everyMinute()
  ->when(function () {
    return getPendingOrderCount() > 0;
  });

输出处理

任务的执行结果可以写入文件或发送邮件:

// 写入单个文件
$scheduler->php('report.php')
    ->output('/var/log/report-output.log');

// 写入多个文件
$scheduler->php('report.php')
    ->output(['/var/log/report.log', '/var/log/report-archive.log']);

// 追加模式(默认是覆盖)
$scheduler->php('report.php')
    ->output('/var/log/report.log', true);

// 发送邮件(需要安装 swiftmailer)
$scheduler->php('report.php')
    ->output('/tmp/report-output.log')
    ->email(['admin@example.com' => 'Admin']);

说实话,邮件功能我不推荐使用,因为它依赖的 SwiftMailer 已经废弃了。生产环境用钩子自己接通知渠道更靠谱。

前置和后置钩子

before()then() 在任务执行前后插入自定义逻辑:

$logger = new Logger();

$scheduler->php('data-sync.php')
    ->before(function () use ($logger) {
        $logger->info('data-sync 开始执行');
    })
    ->then(function ($output) use ($logger) {
        $logger->info('data-sync 执行完毕,输出: ' . $output);
    });

实际项目中,我通常用后置钩子做失败通知 -- 接入企业微信或钉钉机器人,比邮件及时得多。

Worker 模式

调试阶段不想设 crontab,可以用 work() 方法直接在终端里跑:

$scheduler = new \GO\Scheduler();
$scheduler->php('some-job.php')->everyMinute();
$scheduler->work();  // 阻塞式循环,每分钟检查一次

也可以指定检查时间点:

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

// 在每分钟的第 0 秒和第 30 秒各检查一次
$scheduler->work([0, 30]);

这个模式主要用来开发调试,生产环境还是老老实实用 crontab。

完整示例

一个典型的调度文件 scheduler.php

<?php
require __DIR__ . '/vendor/autoload.php';

use GO\Scheduler;

$scheduler = new Scheduler();
$bin = '/usr/bin/php8.2';

// 清理过期 session -- 每小时
$scheduler->call(function () {
    $pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '');
    $pdo->exec("DELETE FROM sessions WHERE updated_at < NOW() - INTERVAL 7 DAY");
})->hourly();

// 数据库备份 -- 每天凌晨 2:00
$scheduler->raw('mysqldump -u root app | gzip > /backup/app_$(date +\%Y\%m\%d).sql.gz')
    ->daily(2)
    ->onlyOne();

// 发送日报 -- 工作日 18:00
$scheduler->php(__DIR__ . '/jobs/daily-report.php', $bin)
    ->daily(18)
    ->when(fn() => date('N') < 6)
    ->output('/var/log/daily-report.log');

// 检查队列积压 -- 每 5 分钟
$scheduler->php(__DIR__ . '/jobs/check-queue.php', $bin)
    ->everyMinute(5)
    ->before(fn() => error_log('queue check start'))
    ->then(function ($output) {
        if (trim($output) === 'ALERT') {
            sendDingTalkNotification('队列积压告警');
        }
    });

$scheduler->run();

然后 crontab 里只加一条:

* * * * * /usr/bin/php /path/to/scheduler.php >> /dev/null 2>&1

全部搞定。新增任务改 PHP 文件就行,不用再碰 crontab。

失败处理

run() 执行后可以拿到失败的任务列表:

$scheduler->run();

foreach ($scheduler->getFailedJobs() as $failedJob) {
    $job = $failedJob->getJob();
    $exception = $failedJob->getException();
    error_log("任务执行失败: " . $exception->getMessage());
}

不过要注意,这个库没有内置重试机制。任务失败了就是失败了,需要自己在钩子里实现重试逻辑。

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

有什么不足

说几个需要注意的点:

五年没发版了。 v4.0 发布于 2021 年,至今没出新版本。社区有 43 个 Open Issue,部分涉及 PHP 8.2+ 的 deprecation warnings。功能上是够用的,但如果你需要活跃维护的项目,要考虑这个风险。

不支持 Windows。 后台执行模式用了 | tee> /dev/null 2>&1 & 这些 Unix 特有的写法,Windows 环境下只有闭包类型的任务能正常工作。

锁机制比较原始。 基于文件的 .lock 锁在单机场景够用,但如果你的应用跑在多台服务器上,需要自己加分布式锁。

没有重试机制。 任务失败后不会自动重试,需要自己在 then() 钩子里处理。

邮件功能过时。 依赖的 SwiftMailer 已经废弃,建议忽略邮件功能,用自己的通知方案。

版权声明:本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。

总结

php-cron-scheduler 做到了一件事:把 Laravel 任务调度的核心体验剥离出来,给不使用 Laravel 的项目用。3 个类、1 个依赖,源码十分钟就能看完,没有学习成本。如果你的项目定时任务超过 3 个,还在手搓 crontab,用它会让生活轻松不少。唯一需要权衡的是维护活跃度 -- 功能成熟但作者不太活跃,如果你介意这一点,可以考虑 Crunz 作为替代。

最近浏览
IP用户:49.51.*.*
48 分钟前 Mobile Safari iOS 13.2
IP用户:112.251.*.*
2 小时前 Firefox Windows 10
累计浏览次数:4
评论
点击登录
phpreturn,PHP武器库,专注PHP领域的项目和资讯,收录和介绍PHP相关项目。

本站所有权利归 phpreturn.com 所有

举报/反馈/投稿邮箱:phpreturn@ulthon.com

鲁ICP备19027671号-2