lordf / php-cron-scheduler
PHP Cron Job Scheduler
Requires
- php: ^7.4 || ^8.0
- dragonmantank/cron-expression: ^3.0
Requires (Dev)
- php-coveralls/php-coveralls: ^2.4
- phpunit/phpunit: ~9.5
- swiftmailer/swiftmailer: ~5.4 || ^6.0
Suggests
- swiftmailer/swiftmailer: Required to send the output of a job to email address/es (~5.4 || ^6.0).
This package is not auto-updated.
Last update: 2024-09-25 04:26:59 UTC
README
这是一个由 https://github.com/peppeocchi/php-cron-scheduler 衍生出来的包,它是一个与框架无关的cron作业调度器,可以轻松集成到您的项目中或作为独立的命令调度器运行。这个想法最初受到了Laravel任务调度的启发。
通过Composer安装
推荐的方式是通过 Composer 安装 php-cron-scheduler。有关如何下载和安装Composer的说明,请参阅入门。
下载/安装Composer后,运行
php composer.phar require lordf/php-cron-scheduler
或将包添加到您的 composer.json
{
"require": {
"lordf/php-cron-scheduler": "5.*"
}
}
调度器需要 php >= 7.4,对于旧版本,请查看原始存储库 https://github.com/peppeocchi/php-cron-scheduler
工作原理
在项目的根目录中创建一个名为 scheduler.php 的文件,并包含以下内容。
<?php require_once __DIR__.'/vendor/autoload.php'; use GO\Scheduler; // Create a new scheduler $scheduler = new Scheduler(); // ... configure the scheduled jobs (see below) ... // Let the scheduler execute jobs which are due. $scheduler->run();
然后向您的crontab添加一个新的条目,每分钟运行 scheduler.php。
* * * * * path/to/phpbin path/to/scheduler.php 1>> /dev/null 2>&1
就是这样!您的调度器已启动并运行,现在您可以添加作业而不用担心crontab了。
调度作业
默认情况下,所有作业都会在后台运行。PHP脚本和原始命令默认在后台运行,而函数始终在前景运行。您可以通过调用 inForeground() 方法强制命令在前景运行。需要将输出发送到电子邮件的作业将在前景运行。
调度PHP脚本
$scheduler->php('path/to/my/script.php');
php 方法接受4个参数
- 您的PHP脚本路径
- 要使用的PHP二进制文件
- 传递给脚本的参数(注意:您需要在您的 php.ini 中启用 register_argc_argv 以使此功能生效(参考)。不用担心,它默认已启用,除非您故意禁用它或您的托管商默认禁用它,否则您可以忽略它。)
- 标识符
$scheduler->php( 'path/to/my/script.php', // The script to execute 'path/to/my/custom/bin/php', // The PHP bin [ '-c' => 'ignore', '--merge' => null, ], 'myCustomIdentifier' );
调度原始命令
$scheduler->raw('ps aux | grep httpd');
raw 方法接受3个参数
- 您的命令
- 传递给命令的参数
- 标识符
$scheduler->raw( 'mycommand | myOtherCommand', [ '-v' => '6', '--silent' => null, ], 'myCustomIdentifier' );
调度函数
$scheduler->call(function () { return true; });
call 方法接受3个参数
- 您的函数
- 传递给函数的参数
- 标识符
$scheduler->call( function ($args) { return $args['user']; }, [ ['user' => $user], ], 'myCustomIdentifier' );
您传递给数组中的所有参数都将注入到您的函数中。例如
$scheduler->call( function ($firstName, $lastName) { return implode(' ', [$firstName, $lastName]); }, [ 'John', 'last_name' => 'Doe', // The keys are being ignored ], 'myCustomIdentifier' );
如果您想传递一个键=>值对,请将一个数组作为参数数组传递
$scheduler->call( function ($user, $role) { return implode(' ', [$user['first_name'], $user['last_name']]) . " has role: '{$role}'"; }, [ [ 'first_name' => 'John', 'last_name' => 'Doe', ], 'Admin' ], 'myCustomIdentifier' );
设置执行时间
有一些方法可以帮助您设置调度的执行时间。如果您没有调用任何此方法,则作业将每分钟运行一次 (* * * * *)。
at- 此方法接受由 dragonmantank/cron-expression 支持的任何表达式$scheduler->php('script.php')->at('* * * * *');
everyMinute- 每分钟运行一次。您可以可选地传递一个$minute来指定作业每$minute分钟运行一次。$scheduler->php('script.php')->everyMinute(); $scheduler->php('script.php')->everyMinute(5);
hourly- 每小时运行一次。你可以选择传递你想要运行的$minute,默认情况下将在每小时的 '00' 分钟运行。$scheduler->php('script.php')->hourly(); $scheduler->php('script.php')->hourly(53);
daily- 每天运行一次。你可以选择传递$hour和$minute以获得更细致的控制(或字符串hour:minute)$scheduler->php('script.php')->daily(); $scheduler->php('script.php')->daily(22, 03); $scheduler->php('script.php')->daily('22:03');
对于工作日还有额外的助手(所有都接受可选的小时和分钟 - 默认为 00:00)
星期天星期一星期二星期三星期四星期五星期六
$scheduler->php('script.php')->saturday(); $scheduler->php('script.php')->friday(18); $scheduler->php('script.php')->sunday(12, 30);
还有额外的月份助手(所有都接受可选的日期、小时和分钟 - 默认为每月的 1 日在 00:00)
一月二月三月四月五月六月七月八月九月十月十一月十二月
$scheduler->php('script.php')->january(); $scheduler->php('script.php')->december(25); $scheduler->php('script.php')->august(15, 20, 30);
你还可以指定一个 date 来指定作业应该运行的时间。日期可以指定为字符串或 DateTime 实例。在两种情况下,你都可以指定日期(例如,2018-01-01)或同时指定时间(例如,2018-01-01 10:30)。如果你不指定时间,它将在该日期的 00:00 运行。如果你提供的是一个“非标准”格式的日期,强烈建议传递一个 DateTime 实例。如果你在未指定时间的情况下使用 createFromFormat,并希望将其默认为 00:00,只需确保在日期格式中添加一个 !,否则时间将是当前时间。 了解更多
$scheduler->php('script.php')->date('2018-01-01 12:20'); $scheduler->php('script.php')->date(new DateTime('2018-01-01')); $scheduler->php('script.php')->date(DateTime::createFromFormat('!d/m Y', '01/01 2018'));
将输出发送到文件
你可以定义一个或多个文件,将你的脚本/命令/函数执行的输出发送到这些文件。
$scheduler->php('script.php')->output([ 'my_file1.log', 'my_file2.log' ]); // The scheduler catches both stdout and function return and send // those values to the output file $scheduler->call(function () { echo "Hello"; return " world!"; })->output('my_file.log');
将输出发送到电子邮件
你可以定义一个或多个电子邮件地址,将你的脚本/命令/函数执行的输出发送到这些地址。为了发送电子邮件,作业的输出需要首先发送到一个文件。实际上,文件将作为附件附加到你的电子邮件地址。为了使这生效,你需要安装 swiftmailer/swiftmailer
$scheduler->php('script.php')->output([ // If you specify multiple files, both will be attached to the email 'my_file1.log', 'my_file2.log' ])->email([ 'someemail@mail.com' => 'My custom name', 'someotheremail@mail.com' ]);
你可以选择使用自定义的 Swift_Mailer 实例和自定义的 Swift_Transport 进行自定义。
subject- 发送电子邮件的主题from- 设置为发送者的电子邮件地址body- 电子邮件正文transport- 要使用的传输方式。例如,如果你想使用你的 Gmail 账户或任何其他 SMTP 账户。该值应该是一个Swift_Tranport实例ignore_empty_output- 如果设置为true,则没有输出的作业不会触发任何电子邮件。
配置可以在创建调度器时为所有调度命令全局设置。
$scheduler = new Scheduler([ 'email' => [ 'subject' => 'Visitors count', 'from' => 'cron@email.com', 'body' => 'This is the daily visitors count', 'transport' => Swift_SmtpTransport::newInstance('smtp.gmail.com', 465, 'ssl') ->setUsername('username') ->setPassword('password'), 'ignore_empty_output' => false, ] ]);
或者可以在作业级别进行设置。
$scheduler = new Scheduler(); $scheduler->php('myscript.php')->configure([ 'email' => [ 'subject' => 'Visitors count', ] ]); $scheduler->php('my_other_script.php')->configure([ 'email' => [ 'subject' => 'Page views count', ] ]);
安排条件执行
有时你可能想要执行一个计划,不仅当执行到期时,还取决于其他一些条件。
你可以使用 when 方法将 cronjob 的执行委托给一个布尔测试。
$scheduler->php('script.php')->when(function () { // The job will run (if due) only when // this function returns true return true; });
安排执行顺序
即将运行的作业按其执行顺序进行排序:可以在 后台 运行的作业将首先执行。
安排重叠
为了防止在之前的执行仍在进行时执行计划,请使用 onlyOne 方法。为了避免重叠,调度器需要创建 锁文件。默认情况下,它将使用用于临时文件的目录路径。
你可以在创建新的调度器实例时全局指定自定义目录路径。
$scheduler = new Scheduler([ 'tempDir' => 'path/to/my/tmp/dir' ]); $scheduler->php('script.php')->onlyOne();
或者你可以在作业级别定义目录路径。
$scheduler = new Scheduler(); // This will use the default directory path $scheduler->php('script.php')->onlyOne(); $scheduler->php('script.php')->onlyOne('path/to/my/tmp/dir'); $scheduler->php('other_script.php')->onlyOne('path/to/my/other/tmp/dir');
在某些情况下,即使任务重叠,你也可能希望运行该任务。例如,如果上次执行超过5分钟前。你可以将一个函数作为第二个参数传递,上次执行时间将被注入。只有在该函数返回false之前,任务才不会运行。如果它返回true,则任务将在重叠时运行。
$scheduler->php('script.php')->onlyOne(null, function ($lastExecutionTime) { return (time() - $lastExecutionTime) > (60 * 5); });
任务执行前
在某些情况下,你可能希望在任务即将运行之前运行一些代码。例如,你可能想要添加一条日志条目,ping一个URL或其他任何操作。为此,你可以像下面示例一样调用before。
// $logger here is your own implementation $scheduler->php('script.php')->before(function () use ($logger) { $logger->info("script.php started at " . time()); });
任务执行后
有时你可能希望在任务运行后执行某些操作。then方法提供了你在任务执行后执行任何操作的能力。任务的输出将被注入到这个函数中。默认情况下,由于输出被注入到函数中,任务将被强制在前景运行。如果你不需要输出,你可以将true作为第二个参数传递,允许在后台执行(在这种情况下,$output将为空)。
// $logger and $messenger here are your own implementation $scheduler->php('script.php')->then(function ($output) use ($logger, $messenger) { $logger->info($output); $messenger->ping('myurl.com', $output); }); $scheduler->php('script.php')->then(function ($output) use ($logger) { $logger->info('Job executed!'); }, true);
一起使用"before"和"then"
// $logger here is your own implementation $scheduler->php('script.php') ->before(function () use ($logger) { $logger->info("script.php started at " . time()); }) ->then(function ($output) use ($logger) { $logger->info("script.php completed at " . time(), [ 'output' => $output, ]); });
多次调度运行
在某些情况下,你可能需要在同一脚本中多次运行调度器。尽管这不是一个常见情况,但以下方法将允许你重用相同的调度器实例。
# some code $scheduler->run(); # ... // Reset the scheduler after a previous run $scheduler->resetRun() ->run(); // now we can run it again
如果你每次运行调度器时都要使用不同的任务(例如,来自外部源的任务,如数据库、文件等),那么一个很有用的方法是清除当前计划的任务。
$scheduler->clearJobs(); $jobsFromDb = $db->query(/*...*/); foreach ($jobsFromDb as $job) { $scheduler->php($job->script)->at($job->schedule); } $scheduler->resetRun() ->run();
模拟调度器运行时间
当运行调度器时,你可能需要传递一个DateTime来模拟调度器的运行时间。这个功能的用途在这里有描述。
// ...
$fakeRunTime = new DateTime('2017-09-13 00:00:00');
$scheduler->run($fakeRunTime);
任务失败
如果某些任务失败了,你可以访问失败任务列表和失败原因。
// get all failed jobs and select first $failedJob = $scheduler->getFailedJobs()[0]; // exception that occurred during job $exception = $failedJob->getException(); // job that failed $job = $failedJob->getJob();
任务时间戳
如果你想知道任务何时开始、完成或失败,你可以访问这些时间戳,它们是DateTime对象,或者在任务未开始时为null。
// Get all failed jobs and select first $failedJob = $scheduler->getFailedJobs()[0]; // job that failed $job = $failedJob->getJob(); // Time, when job was created $job->getCreationTime(); // Time when the job was ordered to perform by Scheduler $job->getRunTime(); // Real time when the job started to run. (After checks, compiling and "before" callback) $job->getRealRunTime(); // Time, when job was finished (Excluding "then" callback and email output) or failed. $job->getFinishTime();
工作者
你可以通过启动一个工作者来模拟cron任务。让我们看看一个简单的例子。
$scheduler = new Scheduler(); $scheduler->php('some/script.php'); $scheduler->work();
上面的代码启动了一个工作者,该工作者会每分钟运行你的任务。这应该是一个测试/调试工具,但你可以自由地按照自己的方式使用它。你可以选择性地传递一个数组,指定工作者运行任务的时间“秒”,例如通过传递[0, 30],工作者将在每分钟的秒0和秒30运行你的任务。
$scheduler->work([0, 10, 25, 50, 55]);
强烈建议你将你的工作者与调度器分开运行,尽管你可以在调度器中运行工作者。问题是,如果你的调度器有一个或多个同步任务,工作者将不得不等待任务完成才能继续循环。例如
$scheduler->call(function () { sleep(120); }); $scheduler->work();
上面的代码将跳过多次执行,因此它不会每分钟运行一次,而是可能会每2或3分钟运行一次。相反,更受欢迎的方法是将工作者与你的调度器分开。
// File scheduler.php $scheduler = new Scheduler(); $scheduler->call(function () { sleep(120); }); $scheduler->run();
// File worker.php $scheduler = new Scheduler(); $scheduler->php('scheduler.php'); $scheduler->work();
然后在命令行中运行php worker.php。这将启动一个前台进程,你可以通过简单地退出命令来终止它。
工作者不是为了收集关于你的运行的任何数据,正如之前所说,它是一个测试/调试工具。