Scheduled jobs (crons)
A cron is a PHP class that Hubleto runs periodically: sending queued e-mails, warning about expiring price lists, synchronizing a calendar. Crons are triggered by one system cron job that runs cron.php every minute.
1. Run the cron runner from the system
* * * * * cd /path/to/your/project && php cron.php
cron.php starts the CronManager, which runs every registered cron whose schedule matches the current minute. You can run a single cron at any time, ignoring its schedule, by passing its class name (as it was registered) as the first argument, e.g. php cron.php 'Hubleto\App\Community\Mail\Crons\SendMails'.
erp-project: cron.php
<?php
// bootstrap
require_once(__DIR__ . "/boot.php");
// run cron
$main->cronManager()->init();
$main->cronManager()->run();
NOTE In the project skeleton at the time of writing,
cron.phpcalls$main->cronManager(), butboot.phpcreates the loader as$hubleto. Until this is fixed, replace$mainwith$hubletoin yourcron.php. Look at the log inlog/when the crons do not run.
2. Write a cron
A cron extends Hubleto\Erp\Cron, defines $schedulingPattern and implements run():
src/Cron.php
<?php declare(strict_types=1);
namespace Hubleto\Erp;
class Cron extends \Hubleto\Erp\Core
{
// CRON-formatted string specifying the scheduling pattern
public string $schedulingPattern = '*/5 * * * *';
public function run(): void
{
// to be overriden
}
}
The default pattern of Hubleto\Erp\Cron is */5 * * * * (every 5 minutes). The $schedulingPattern has five fields: minute hour day-of-month month day-of-week. Each field can be:
| Value | Meaning |
|---|---|
* |
every value |
*/5 |
every 5th value (*/5 * * * * = every 5 minutes) |
7 |
exactly this value |
Supported syntax.
Lists (1,15) and ranges (1-5) are not supported. The day of the week starts with 0 = Monday. The cron is checked once per minute, so the pattern * * * * * runs it every minute.
A cron of an app
apps/PriceLists/Crons/WarnAboutExpiry.php
<?php
namespace Hubleto\App\Community\PriceLists\Crons;
use Hubleto\App\Community\Notifications\Sender;
use Hubleto\App\Community\PriceLists\Models\PriceList;
use Hubleto\App\Community\PriceLists\Services\Counter;
/**
* Notifies owners of price lists that are about to expire (within the setting expiryWarningDays).
* Notifications are sent with Notifications\Sender.
*/
class WarnAboutExpiry extends \Hubleto\Erp\Cron
{
public string $schedulingPattern = '0 7 * * *';
public function run(): void
{
/** @var Counter $counter */
$counter = $this->getService(Counter::class);
/** @var Sender $sender */
$sender = $this->getService(Sender::class);
foreach ($counter->expiringPriceLists() as $priceList) {
$idOwner = (int) ($priceList['id_owner'] ?? 0);
if ($idOwner <= 0) continue;
$sender->send(
900,
['price-lists', 'expiry'],
PriceList::class,
(int) $priceList['id'],
$idOwner,
$this->translate('Price list is about to expire'),
$priceList['code'] . ' ' . $priceList['name'] . ' - ' . $this->translate('valid to') . ' ' . $priceList['valid_to'],
'price-lists/' . $priceList['id']
);
}
}
}
3. Register the cron
Register it in init() of the Loader of the app:
apps/PriceLists/Loader.php (excerpt)
$this->cronManager()->addCron(Crons\ActivateAndExpire::class);
$this->cronManager()->addCron(Crons\WarnAboutExpiry::class);
}
/**
* Installs the app: creates tables (round 1) and seeds default data (round 2).
*
* @param int $round
*
* @return void
*
*/
public function installApp(int $round): void
{
if ($round == 1) {
$modelClasses = [
Models\PriceList::class, Models\Item::class,
Models\Assignment::class, Models\Rule::class, Models\History::class, Models\Resolution::class,
Models\PriceListHasDocument::class,
];
foreach ($modelClasses as $modelClass) {
/** @var \Hubleto\Erp\Model $model */
$model = $this->getModel($modelClass);
$model->upgradeSchema();
}
}
addCron() accepts only classes that extend Hubleto\Erp\Cron. The cron is created with getService(), so it is a singleton.
Crons of a project
Crons that do not belong to an app go to src/crons of the project. The CronManager scans the folder and registers every class in the namespace HubletoProject\Cron:
erp-project: src/crons/Example.php
<?php
/*
This cron does not do anything. It is just an example of how the
cron should be implemented.
*/
namespace HubletoProject\Cron;
class Example extends \Hubleto\Erp\Cron
{
// CRON-formatted string specifying the scheduling pattern
public string $schedulingPattern = '0 0 * * *';
public function run(): void
{
$this->logger()->info("Sample cron started.");
}
}
Writing a good cron
- Be idempotent. A cron may run twice (a failed run is retried by the system cron). Mark processed records.
- Process a limited batch.
Mail\Crons\SendMailssends at most 10 messages per run. - Do not rely on the user. There is no session in a cron:
authProvider()->getUserId()is0. Services that need a signed-in user (such asNotifications\Sender) do not work from a cron. - Log. Use
$this->logger()->info()anderror(). TheCronManagerlogs into thecronlog. - Catch exceptions. The manager catches them, logs them and goes on with the next cron.
