macropage/php-cron-scheduler

PHP Cron Job Scheduler - peppeocchi/php-cron-scheduler 的分支

v4.0 2021-04-22 21:32 UTC

README

添加包

phpseclib/phpseclib
spatie/fork
  • 添加一个执行远程 ssh 命令的新 ssh 命令
  • spatie/fork 的帮助下并行运行作业
  • 原始包的一些功能不再工作
  • 作业输出(和其他信息)作为序列化字符串返回

PHP Cron Scheduler

Latest Stable Version License Build Status Coverage Status StyleCI Total Downloads

这是一个与框架无关的 cron 作业调度器,可以轻松集成到您的项目中或作为独立命令调度器运行。这个想法最初受到了 Laravel 任务调度 的启发。

通过 Composer 安装

建议通过 Composer 安装 php-cron-scheduler。有关如何下载和安装 Composer 的说明,请参阅 入门

下载/安装 Composer 后,请运行

php composer.phar require peppeocchi/php-cron-scheduler

或将包添加到您的 composer.json

{
    "require": {
        "macropage/php-cron-scheduler": "dev-master"
    }
}

工作原理

在项目的根目录中创建一个名为 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_Transport 来自定义 Swift_Mailer 实例。您可以配置

  • 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 方法将 cron 作业的执行委托给一个布尔测试。

$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方法提供了在作业执行后执行任何您想要的操作的灵活性。作业的输出将被注入到这个函数中。例如,您可能想添加日志条目、ping一个url等。默认情况下,作业将强制在前台运行(因为输出被注入到函数中),如果您不需要输出,您可以传递第二个参数为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();

工作员

您可以通过启动工作员来模拟cron作业。让我们看看一个简单的示例

$scheduler = new Scheduler();
$scheduler->php('some/script.php');
$scheduler->work();

上面的代码启动了一个工作员,它将每分钟运行您的作业。这是一个测试/调试工具,但您可以自由地按您喜欢的任何方式使用它。您可以可选地传递一个包含您希望工作员运行作业的“秒数”的数组,例如,通过传递[0, 30],工作员将在每分钟的强项030秒运行您的作业。

$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。这将启动一个前台进程,您可以通过简单地退出命令来将其终止。

工作员不是为了收集关于您的运行的任何数据,并且如前所述,它是一个测试/调试工具。

许可

MIT许可(MIT)