Cron systém

Cron joby jsou v app/cron/jobs/. Jediný soubor spouštěný z crontabu je run.php, který každou minutu prochází složky a podle názvu souborů rozhodne, co spustit.

Struktura

app/cron/
├── CronVariable.php         // Persistentní proměnné mezi běhy
├── PreventParallelJobRun.php // Zamezení paralelnímu běhu
└── jobs/
    ├── run.php              // Hlavní dispatcher (spouští se každou minutu)
    ├── admin/
    │   ├── every_1min.php
    │   └── daily_at_08_30.php
    └── reports/
        ├── every_1h.php
        └── weekly_at_1.php

Crontab

# Jediný záznam v crontabu — spouští run.php každou minutu
* * * * * /usr/bin/php /var/www/app/cron/jobs/run.php

Vzory souborů

Název souboru určuje frekvenci spouštění. Soubory se umísťují do podsložek (admin/, reports/, ...).

Minutové intervaly

every_1min.phpKaždou minutu
every_5min.phpKaždých 5 minut (0, 5, 10, 15, ...)
every_10min.phpKaždých 10 minut (0, 10, 20, 30, ...)
every_15min.phpKaždých 15 minut (0, 15, 30, 45)
every_20min.phpKaždých 20 minut (0, 20, 40)
every_30min.phpKaždých 30 minut (0, 30)

Hodinové intervaly

every_1h.phpKaždou hodinu v 0. minutě
every_3h.phpKaždé 3 hodiny (0:00, 3:00, 6:00, ...)
every_6h.phpKaždých 6 hodin (0:00, 6:00, 12:00, 18:00)
every_12h.phpKaždých 12 hodin (0:00, 12:00)

Denní a týdenní

daily.phpKaždý den o půlnoci (0:00)
daily_at_XX_XX.phpKaždý den v konkrétní čas. Např. daily_at_08_30.php = 8:30.
weekly.phpKaždé pondělí o půlnoci
weekly_at_X.phpKaždý týden v konkrétní den o půlnoci. 1=Po, 7=Ne. Např. weekly_at_5.php = pátek.
Všechny časy jsou v timezone serveru, ne v UTC.

Vytvoření cron jobu

Příkaz php moony make cron-job vytvoří nový cron job. Formát: "složka/pattern" — složka je povinná.

php moony make cron-job "admin/every_1min"
php moony make cron-job "reports/daily_at_08_30"
php moony make cron-job "cleanup/weekly_at_5"

Příkaz automaticky:

  • Zvaliduje pattern (musí být jeden z platných vzorů)
  • Vytvoří složku pokud neexistuje
  • Vygeneruje soubor s namespace podle cesty
  • Nastaví správnou relativní cestu k bootstrap.php

Vygenerovaná šablona

Příklad pro php moony make cron-job "admin/every_1min":

<?php
namespace Moony\app\cron\jobs\admin;

use Moony\app\cron\PreventParallelJobRun;
use Moony\app\repositories\system\CronHistoryRepository;
use Moony\bootstrap\core\facades\DB;

require __DIR__ . '/../../../../bootstrap.php';

class every_1min extends PreventParallelJobRun
{
    private array $data = [];

    public function __construct()
    {
        parent::__construct();

        $this->run();
        $this->finish();
    }

    private function run(): void
    {
        // Cron job work

        // Volitelně — uložit data do historie
        $this->data['processed'] = 42;
        $this->data['errors'] = 0;
    }

    private function finish(): void
    {
        if(DB::isConnected()) {
            CronHistoryRepository::updateBy([
                'folder' => 'admin',
                'class' => 'every_1min',
                'finished_at' => null,
            ], [
                'finished_at' => (new \DateTime())->format('Y-m-d H:i:s.u'),
                'data' => !empty($this->data) ? json_encode($this->data, JSON_UNESCAPED_UNICODE) : null,
            ]);
        }
    }
}

if(isset($argv[0]) && basename(__FILE__) === basename($argv[0])) {
    (new every_1min());
}

Jak to funguje

PreventParallelJobRunZabraňuje spuštění jobu, pokud předchozí běh ještě neskončil (file lock)
$dataPrivátní pole — v run() do něj zapsat výsledky/statistiky, po skončení se uloží jako JSON do DB
run()Hlavní logika cron jobu
finish()Po dokončení run() zapíše finished_at a $data (jako JSON) do cron_history
if(isset($argv[0])...)Spouštěcí guard — job se spustí jen z CLI, ne při require/include
run.php spouští joby přes exec/pclose a okamžitě se odpojí (fire & forget). Nesleduje výsledek — každý job běží nezávisle.

CronVariable

Pro persistenci dat mezi běhy cron jobů (např. poslední zpracované ID, checkpoint) slouží CronVariable. Data se ukládají serializovaná do DB.

CronVariable::get(string $key)Přečte uloženou hodnotu (libovolný typ). Vrátí null pokud neexistuje.
CronVariable::set(string $key, mixed $value)Uloží hodnotu (UPSERT — vytvoří nebo přepíše)
// Uložení checkpointu
CronVariable::set('last_processed_id', $lastId);

// Čtení při dalším běhu
$lastId = CronVariable::get('last_processed_id') ?? 0;

Historie (cron_history)

Každé spuštění se zaloguje do tabulky cron_history se začátkem a koncem. Datetime má přesnost na 1 desetinné místo.

idINT UNSIGNED AUTO_INCREMENT
folderVARCHAR(255) — název složky (admin, reports, ...)
classVARCHAR(255) — název třídy/souboru (every_1min, daily, ...)
started_atDATETIME(1) NOT NULL — čas spuštění s desetinami sekundy
finished_atDATETIME(1) NULL — čas dokončení (null = stále běží nebo spadl)
dataMEDIUMTEXT NULL — volitelná JSON data z cron jobu (statistiky, výsledky)

DDL

CREATE TABLE IF NOT EXISTS `cron_history` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `folder` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_general_ci',
    `class` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_general_ci',
    `started_at` DATETIME(1) NOT NULL,
    `finished_at` DATETIME(1) NULL DEFAULT NULL,
    `data` MEDIUMTEXT NULL DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    INDEX `idx_folder_class` (`folder`, `class`) USING BTREE,
    INDEX `idx_started_at` (`started_at`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
Záznam s finished_at = NULL znamená, že job stále běží nebo havaroval. Lze použít pro monitoring a alerting.