lavary/crunz

此包已被弃用且不再维护。作者建议使用 crunzphp/crunz 包。

直接从代码中安排您的任务。

资助包维护!
PabloKowalczyk

安装次数: 1,265,105

依赖者: 7

建议者: 0

安全: 0

星标: 1,415

关注者: 50

分支: 148

v3.2.1 2022-01-12 14:34 UTC

README

Crunz

一次性安装定时任务,其余通过代码管理。

Crunz 是一个框架无关的包,用于通过流畅的 API 在 PHP 中安排周期性任务(cron 任务)。

Crunz 能够执行任何类型的可执行命令以及 PHP 闭包。

Version Packagist Packagist

版本 支持的 PHP 版本
dev v3 (v3.2-dev) 7.4+
稳定 v3 (v3.1.0) 7.4+
稳定 v2 (v2.3.1) 7.2+
稳定 v1 (v1.12.4) 5.6-7.0+

路线图

版本 发布日期 活跃支持至 错误支持至 状态
v1.x 2016 年 4 月 2019 年 4 月 2020 年 4 月 结束生命周期
v2.x 2019 年 4 月 2021 年 4 月 2022 年 4 月 错误支持
v3.x 2021 年 4 月 2023 年 4 月 2024 年 4 月 活跃支持

安装

要安装它

composer require lavary/crunz

如果安装成功,将有一个名为 crunz 的命令行工具被链接到您的项目的 vendor/bin 目录。

它是如何工作的?

这个想法非常简单:我们不是在 crontab 文件中安装 cron 任务,而是使用 Crunz 接口在 PHP 文件中定义它们。

以下是一个基本示例

<?php
// tasks/backupTasks.php

use Crunz\Schedule;

$schedule = new Schedule();
$task = $schedule->run('cp project project-bk');       
$task->daily();

return $schedule;

要运行任务,您只需要安装一个普通的 cron 任务(一个 crontab 条目),该任务每分钟运行一次,并将责任委托给 Crunz 的事件运行器

* * * * * cd /project && vendor/bin/crunz schedule:run

schedule:run 命令负责收集所有 PHP 任务文件并运行即将到期的任务。

任务文件

任务文件类似于 crontab 文件。就像 crontab 文件一样,它们可以包含一个或多个任务。

通常,我们在项目根目录下的 tasks/ 目录中创建我们的任务文件。

默认情况下,Crunz 假设所有任务文件都位于项目根目录下的 tasks/ 目录中。

有两种指定源目录的方法:1) 配置文件 2) 作为事件运行器命令的参数。

我们可以通过将路径传递给事件运行器作为参数来显式设置源路径

* * * * * cd /project && vendor/bin/crunz schedule:run /path/to/tasks/directory

创建一个简单的任务

在终端中,切换到您的项目根目录,并运行以下命令

mkdir tasks && cd tasks
nano GeneralTasks.php

然后,添加以下任务

<?php
// tasks/FirstTasks.php

use Crunz\Schedule;

$schedule = new Schedule();

$task = $schedule->run('cp project project-bk'); 
$task
    ->daily()
    ->description('Create a backup of the project directory.');

// ...

// IMPORTANT: You must return the schedule object
return $schedule; 

创建任务文件有一些约定,您需要遵守。首先,文件名应以 Tasks.php 结尾,除非我们通过配置设置更改此设置。

除此之外,我们必须在每个文件的末尾返回 Schedule 类的实例,否则,事件运行器将跳过文件内的所有任务。

由于 Crunz 递归地扫描任务目录,我们可以根据使用情况将所有任务放在一个文件中,或者分布在不同的文件(或目录)中。这种行为有助于我们拥有一个井然有序的任务目录。

命令

我们可以通过使用 run() 来运行任何命令或脚本。此方法接受两个参数:要执行的 命令,以及如果有,命令选项(作为关联数组)。

正常命令或脚本

<?php

use Crunz\Schedule;

$schedule = new Schedule();
$task = $schedule->run(PHP_BINARY . ' backup.php', ['--destination' => 'path/to/destination']);
$task
    ->everyMinute()
    ->description('Copying the project directory');

return $schedule;

在上面的例子中,--destination 是由 backup.php 脚本支持的选项。

闭包

我们也可以写入一个闭包而不是命令。

<?php

use Crunz\Schedule;

$schedule = new Schedule();

$x = 12;
$task = $schedule->run(function() use ($x) { 
   // Do some cool stuff in here 
});

$task
    ->everyMinute()
    ->description('Copying the project directory');

return $schedule;

执行频率

有多种方法可以指定任务应该运行的时间以及频率。我们可以将这些方法结合起来,以获得所需的频率。

时间单位

有一组方法指定时间单位(大于分钟),通常以 ly 后缀结尾,例如 hourly()daily()weeklymonthly()quarterly()yearly

使用这组方法安排的所有事件都发生在该时间单位的开头。例如,weekly() 将在星期日运行事件,而 monthly() 将在每月的第一天运行。

以下任务将在每天午夜(每日时间段的开始)运行。

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' backup.php');    
$task->daily();
// ...

这里还有一个,它将在每月的第一天运行。

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php');
$task->monthly();
// ...

在特定时间运行事件

要安排一次性任务,您可以使用以下格式的 on() 方法

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php'); 
$task->on('13:30 2016-03-01');
// ...

上述任务将在 2016 年 3 月 1 日下午 1:30 运行。

On() 接受 PHP 的 strtotime 函数解析的任何日期格式。

要指定任务的日期,我们使用 at() 方法

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php'); 
$task
    ->daily()
    ->at('13:30');
// ...

如果我们只传递给 on() 方法一个时间,它将具有与使用 at() 相同的效果。

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php');   
$task
    ->daily()
    ->on('13:30');
         
// is the sames as
$task = $schedule->run(PHP_BINARY . ' email.php');       
$task
    ->daily()
    ->at('13:30');
// ...

如果我们希望,可以在单个语句中将“时间单位”方法(例如 daily()、monthly())与 at() 或 on() 约束结合起来。

以下任务将每小时在 15 分钟运行。

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' feedmecookie.php'); 
$task
    ->hourlyAt('15');
// ...

可以使用 hourlyOn('15') 替代 hourlyAt('15') 以达到相同的结果。

以下任务将在星期一 13:30 运行。

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' startofwork.php'); 
$task
    ->weeklyOn(1,'13:30');
// ...

星期日被视为一周的第 0 天。

如果我们希望任务在星期二(一周的第 2 天)上午 9:00 运行,我们会使用

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' startofwork.php'); 
$task
    ->weeklyOn(2,'09:00');
// ...

任务生命周期

在 crontab 条目中,我们无法轻松地指定任务的生命周期(任务活跃的持续时间)。然而,Crunz 已经使其变得简单。

<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->from('12:30 2016-03-04')
    ->to('04:55 2016-03-10');
 //       

或者,我们可以使用 between() 方法来达到相同的结果。

<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->between('12:30 2016-03-04', '04:55 2016-03-10');

 //       

如果我们没有指定日期部分,任务将每天活跃,但仅在指定的持续时间之内。

<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
     ->everyFiveMinutes()
     ->between('12:30', '04:55');

 //       

上述任务每天在下午12:30至下午4:55之间,每五分钟运行一次。

以下是一个限制任务每小时仅在特定分钟范围内运行的示例

<?php
//

$hour = date('H');
$startminute = $hour.':05';
$endminute = $hour.':15';

$task = $schedule->run(PHP_BINARY . ' email.php');
$task
     ->hourly()
     ->between($startminute, $endminute);

 //       

上述任务每小时在5分钟到15分钟之间运行

工作日

Crunz提供了一套方法,用于指定一周中的某一天。

  • mondays()
  • tuesdays()
  • ...
  • sundays()
  • weekedays()
  • weekends()

这些方法被设计成作为约束使用,不应单独使用。原因是工作日方法仅修改cron作业表达式的“星期几”字段。

考虑以下示例

<?php
// Cron equivalent:  * * * * 1
$task = $schedule->run(PHP_BINARY . ' startofwork.php');
$task->mondays();

乍一看,任务似乎每周一运行,但由于它只修改cron作业表达式的“星期几”字段,任务在每周一每分钟都会运行。

这是使用工作日方法的正确方式

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' startofwork.php');
$task    
    ->mondays()
    ->at('13:30');

// ...

(一个类似的结果但更容易阅读的替代方案 -> weeklyOn(0,'13:30'),如前一个示例所示)

经典方法

我们也可以用旧方法进行调度,就像我们在crontab文件中做的那样

<?php

$task = $schedule->run(PHP_BINARY . ' email.php');
$task->cron('30 12 * 5-6,9 Mon,Fri');

设置单个字段

Crunz的方法不仅限于前面解释的现成方法。我们还可以设置单个字段来组合自定义频率,类似于经典crontab组合它们的方式。这些方法接受一个值数组,或用逗号分隔的列表参数

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php');
$task       
    ->minute(['1-30', 45, 55])
    ->hour('1-5', 7, 8)
    ->dayOfMonth(12, 15)
    ->month(1);

或者

<?php
// ...
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->minute('30')
    ->hour('13')
    ->month([1,2])
    ->dayofWeek('Mon', 'Fri', 'Sat');

// ...

根据我们的用例,我们可以选择并组合适当的集合,这更容易使用。

运行条件

在传统的crontab文件中,我们无法很容易地设置任务的运行条件。通过when()skip()方法,这已经变得简单。

考虑以下代码

<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->between('12:30 2016-03-04', '04:55 2016-03-10')
    ->when(function() {
        if ((bool) (time() % 2)) {
            return true;
        }
        
        return false;
    });

方法when()接受一个回调,如果任务需要运行,则回调必须返回TRUE。这在我们需要在执行资源密集型任务之前检查资源时非常有用。

我们也可以使用skip()方法在特定条件下跳过任务。如果传递的回调返回TRUE,则任务将被跳过。

<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->between('12:30 2016-03-04', '04:55 2016-03-10')
    ->skip(function() {
        if ((bool) (time() % 2)) {
            return true;
        }
        
        return false;  
    });

 //       

我们可以为单个任务多次使用这些方法。它们按顺序评估。

更改目录

您可以使用in()方法在运行命令之前更改目录

<?php

// ...

$task = $schedule->run('./deploy.sh');
$task
    ->in('/home')
    ->weekly()
    ->sundays()
    ->at('12:30')
    ->appendOutputTo('/var/log/backup.log');

// ...

return $schedule;

并行和锁定机制

Crunz并行(在单独的进程)运行计划中的事件,因此所有具有相同执行频率的事件都将异步同时运行。为了实现这一点,Crunz利用symfony/Process库在子进程中运行任务。

如果一个任务的执行时间延长到下一个间隔甚至更长,我们称同一任务实例是重叠的。在某些情况下,这并不是问题。但是,当这些任务修改数据库数据或文件时,可能会引起意外的行为,甚至数据丢失。

为了防止关键任务相互重叠,Crunz通过preventOverlapping()方法提供了一种锁定机制,该机制确保如果有之前的实例正在运行,则不会运行任何任务。

<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->preventOverlapping();
 //       

默认情况下,Crunz使用基于文件的锁定(如果没有传递参数给preventOverlapping)。对于替代的锁定机制,Crunz使用symfony/lock组件,该组件提供了具有各种存储的锁定机制。要使用此组件,可以将存储传递给preventOverlapping()方法。在下面的示例中,使用基于文件的FlockStore提供了替代的锁定文件路径。

<?php

use Symfony\Component\Lock\Store\FlockStore;

$store = new FlockStore(__DIR__ . '/locks');
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->preventOverlapping($store);

截至Symfony 5.0,StoreInterface已拆分为BlockingStoreInterfacePersistingStoreInterface。要使用任何持久性锁(Redis、PDO等),它们需要通过RetryTillSaveStore进行装饰。

<?php

use Symfony\Component\Lock\Store\RedisStore;
use Symfony\Component\Lock\Store\RetryTillSaveStore;

$redis = new Redis();
$redis->connect('localhost');
$persistingStore = new RedisStore($redis);
$blockingStore = new RetryTillSaveStore($persistingStore);

$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->preventOverlapping($blockingStore);

保留输出

Cron作业通常有输出,这通常通过电子邮件发送给crontab文件的拥有者,或者通过crontab文件中的MAILTO环境变量设置的用戶。

我们也可以使用>>>运算符将标准输出重定向到物理文件。

* * * * * /command/to/run >> /var/log/crons/cron.log

这种输出日志记录已经在Crunz中自动化。要将每个事件的输出自动发送到日志文件,我们可以相应地设置配置文件中的log_outputoutput_log_file选项。

# Configuration settings

## ...
log_output:      true
output_log_file: /var/log/crunz.log
## ...

这会将事件的输出(如果成功执行)发送到/var/log/crunz.log文件。然而,我们需要确保我们有权限写入相应的文件。

如果我们需要基于事件记录输出,我们可以使用appendOutputTo()sendOutputTo()方法,如下所示

<?php
//
$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->appendOutputTo('/var/log/crunz/emails.log');

 //       

方法appendOutputTo()将输出追加到指定的文件。要在每个运行后用新数据覆盖日志文件,我们使用saveOutputTo()方法。

我们还可以通过在配置文件中设置email_outputmailer设置,将错误作为电子邮件发送给一组收件人。

错误处理

Crunz通过记录错误并允许在发生错误时添加一组回调,使得错误处理变得简单。

错误回调

您可以根据需要设置任意数量的回调,以在发生错误时运行。

<?php

use Crunz\Schedule;

$schedule = new Schedule();

$task = $schedule->run('command/to/run');
$task->everyFiveMinutes();

$schedule
->onError(function() {
   // Send mail
})
->onError(function() {
   // Do something else
});

return $schedule;

如果发生错误,将执行两个定义的回调。

错误日志

为了记录每次运行期间可能发生的错误,我们可以在配置文件中设置以下log_errorerror_log_file设置

# Configuration settings

# ...
log_errors:      true
errors_log_file: /var/log/error.log
# ...

结果,如果某个事件由于某些原因执行失败,错误消息将被追加到指定的错误日志文件。每个条目都提供了有用的信息,包括发生的时间、事件描述、导致错误的执行命令以及错误消息本身。

我们还可以通过在配置文件中设置email_errormailer设置,将错误作为电子邮件发送给一组收件人。

自定义日志记录器

要使用自己的日志记录器,请创建一个实现了 \Crunz\Application\Service\LoggerFactoryInterface 接口的类,例如:

<?php

namespace Vendor\Package;

use Crunz\Application\Service\ConfigurationInterface;
use Crunz\Application\Service\LoggerFactoryInterface;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;

final class MyEchoLoggerFactory implements LoggerFactoryInterface
{
    public function create(ConfigurationInterface $configuration): LoggerInterface
    {
        return new class extends AbstractLogger {
            /** @inheritDoc */
            public function log(
                $level,
                $message,
                array $context = array()
            ) {
                echo "crunz.{$level}: {$message}";   
            }
        };
    }
}

然后在配置中使用这个类名

# ./crunz.yml file
 
logger_factory: 'Vendor\Package\MyEchoLoggerFactory'

完成。

预处理和后处理钩子

有时我们希望在事件前后执行某些操作。通过将预处理和后处理回调附加到相应的事件,可以实现这一点。

为此,我们在 EventSchedule 对象上使用 before()after(),这意味着我们可以在事件级别以及调度级别上拥有预和后钩子。绑定到调度的钩子将在所有事件之前运行,并在所有事件完成后运行。

<?php

use Crunz\Schedule;

$schedule = new Schedule();

$task = $schedule->run(PHP_BINARY . ' email.php');
$task
    ->everyFiveMinutes()
    ->before(function() { 
        // Do something before the task runs
    })
    ->before(function() { 
        // Do something else
    })
    ->after(function() {
        // After the task is run
    });
 
$schedule
    ->before(function () {
       // Do something before all events
    })
    ->after(function () {
       // Do something after all events are finished
    })
    ->before(function () {
       // Do something before all events
    });

我们可能需要多次使用这些方法,可以通过链式调用它们。

只有在事件执行成功后,才会调用后执行回调。

其他有用命令

我们已经使用了一些 crunz 命令,如 schedule:runpublish:config

要查看 crunz 的所有有效选项和参数,我们可以运行以下命令:

vendor/bin/crunz --help

列出任务

其中这些命令之一是 crunz schedule:list,它以表格格式列出定义的任务(在收集的 *.Tasks.php 文件中)。

vendor/bin/crunz schedule:list

+---+---------------+-------------+--------------------+
| # | Task          | Expression  | Command to Run     |
+---+---------------+-------------+--------------------+
| 1 | Sample Task   | * * * * 1 * | command/to/execute |
+---+---------------+-------------+--------------------+

强制运行

在开发过程中,强制运行所有任务(无论其实际运行时间如何)可能很有用,这可以通过将 --force 添加到 schedule:run 来实现。

vendor/bin/crunz schedule:run --force

要强制运行单个任务,请使用上面的 schedule:list 命令确定任务编号,然后按如下方式运行:

vendor/bin/crunz schedule:run --task 1 --force

生成任务

还有一个有用的命令名为 make:task,它生成一个具有所有默认值的任务文件骨架,这样我们就不必从头开始编写它们。我们可以在根据我们的需求修改输出文件。

例如,要创建一个每分钟运行 /var/www/script.php 的任务,我们运行以下命令:

vendor/bin/crunz make:task exampleOne --run scripts.php --in /var/www --frequency everyHour --constraint mondays
Where do you want to save the file? (Press enter for the current directory)

当我们运行此命令时,Crunz 将询问我们想要保存文件的路径。默认情况下,它位于我们的源任务目录中。

因此,事件定义在指定的任务目录中的 exampleOneTasks.php 文件中。

要查看事件是否已成功创建,请列出事件:

crunz schedule:list

+---+------------------+-------------+----------------+
| # | Task             | Expression  | Command to Run |
+---+------------------+-------------+----------------+
| 1 | Task description | 0 * * * 1 * | scripts.php    |
+---+------------------+-------------+----------------+

要查看 make:task 命令的所有选项及其默认值,请运行以下命令:

vendor/bin/crunz make:task --help

任务调试

以显示任务运行的基本信息

vendor/bin/crunz task:debug 1

上述命令应该输出类似以下的内容:

+----------------------+-----------------------------------+
| Debug information for task '1'                           |
+----------------------+-----------------------------------+
| Command to run       | php -v                            |
| Description          | Inner task                        |
| Prevent overlapping  | No                                |
+----------------------+-----------------------------------+
| Cron expression      | * * * * *                         |
| Comparisons timezone | Europe/Warsaw (from config)       |
+----------------------+-----------------------------------+
| Example run dates                                        |
| #1                   | 2020-03-08 09:27:00 Europe/Warsaw |
| #2                   | 2020-03-08 09:28:00 Europe/Warsaw |
| #3                   | 2020-03-08 09:29:00 Europe/Warsaw |
| #4                   | 2020-03-08 09:30:00 Europe/Warsaw |
| #5                   | 2020-03-08 09:31:00 Europe/Warsaw |
+----------------------+-----------------------------------+

配置

Crunz 以 YAML 格式提供了一些配置选项。要修改配置设置,强烈建议您保留自己的配置文件副本,而不是修改原始文件。

要创建配置文件的副本,首先我们需要发布配置文件

/project/vendor/bin/crunz publish:config
The configuration file was generated successfully

结果,配置文件的副本将创建在我们的项目根目录中。

配置文件看起来像这样:

# Crunz Configuration Settings

# This option defines where the task files and
# directories reside.
# The path is relative to the project's root directory,
# where the Crunz is installed (Trailing slashes will be ignored).
source: tasks

# The suffix is meant to target the task files inside the ":source" directory.
# Please note if you change this value, you need
# to make sure all the existing tasks files are renamed accordingly.
suffix: Tasks.php

# Timezone is used to calculate task run time
# This option is very important and not setting it is deprecated
# and will result in exception in 2.0 version.
timezone: ~

# This option define which timezone should be used for log files
# If false, system default timezone will be used
# If true, the timezone in config file that is used to calculate task run time will be used
timezone_log: false

# By default the errors are not logged by Crunz
# You may set the value to true for logging the errors
log_errors: false

# This is the absolute path to the errors' log file
# You need to make sure you have the required permission to write to this file though.
errors_log_file:

# By default the output is not logged as they are redirected to the
# null output.
# Set this to true if you want to keep the outputs
log_output: false

# This is the absolute path to the global output log file
# The events which have dedicated log files (defined with them), won't be
# logged to this file though.
output_log_file:

# By default line breaks in logs aren't allowed.
# Set the value to true to allow them.
log_allow_line_breaks: false

# By default empty context arrays are shown in the log.
# Set the value to true to remove them.
log_ignore_empty_context: false

# This option determines whether the output should be emailed or not.
email_output: false

# This option determines whether the error messages should be emailed or not.
email_errors: false

# Global Swift Mailer settings
#
mailer:
    # Possible values: smtp, mail, and sendmail
    transport: smtp
    recipients:
    sender_name:
    sender_email:


# SMTP settings
#
smtp:
    host:
    port:
    username:
    password:
    encryption:

如您所见,有几个选项,如用于指定源任务目录的source,其他选项用于错误/输出记录/电子邮件目的。

每次我们运行Crunz命令时,它都会查看项目的根目录,看是否有用户修改过的配置文件。如果配置文件不存在,它将使用随包提供的配置文件。

开发环境标志

以下环境标志仅在开发时使用。典型最终用户不需要也不应该更改它们。

CRUNZ_CONTAINER_DEBUG

用于启用/禁用容器调试模式的标志,仅在开发时有用。默认在docker-compose中启用。

CRUNZ_DEPRECATION_HANDLER

用于启用/禁用Crunz弃用处理器的标志,仅在集成测试时有用。默认为测试禁用。

贡献

我应该选择哪个分支?

在大多数情况下,您应该针对1.12.x分支,因为这是活动开发分支。分支master是用于未来发布,但所有错误/功能都应该提交到1.11.x分支。

如果您需要帮助

请使用GitHub问题提交所有问题和疑问,我会尽力帮助您。

致谢

许可证

Crunz是免费软件,根据MIT许可证条款分发。