cron
This commit is contained in:
@@ -17,7 +17,7 @@ final class ModuleCronScheduler
|
||||
public function definitions(string $moduleName): array
|
||||
{
|
||||
$module = $this->modules->get($moduleName);
|
||||
$tasks = is_array($module['cron_tasks'] ?? null) ? $module['cron_tasks'] : [];
|
||||
$tasks = is_array($module['scheduler_jobs'] ?? null) ? $module['scheduler_jobs'] : (is_array($module['cron_tasks'] ?? null) ? $module['cron_tasks'] : []);
|
||||
$definitions = [];
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
@@ -35,6 +35,7 @@ final class ModuleCronScheduler
|
||||
'name' => $name,
|
||||
'label' => trim((string) ($task['label'] ?? $name)),
|
||||
'callback' => $callback,
|
||||
'mode' => in_array((string) ($task['mode'] ?? 'single'), ['single', 'multi'], true) ? (string) ($task['mode'] ?? 'single') : 'single',
|
||||
'default_enabled' => array_key_exists('default_enabled', $task) ? (bool) $task['default_enabled'] : false,
|
||||
'default_cron' => trim((string) ($task['default_cron'] ?? '0 * * * *')),
|
||||
'default_timezone' => trim((string) ($task['default_timezone'] ?? 'UTC')) ?: 'UTC',
|
||||
@@ -61,45 +62,50 @@ final class ModuleCronScheduler
|
||||
$statuses = [];
|
||||
|
||||
foreach ($definitions as $name => $definition) {
|
||||
$state = $states[$name] ?? [];
|
||||
$config = $this->jobConfig($definition, $settings);
|
||||
$timezone = $this->safeTimezone((string) ($config['timezone'] ?? 'UTC'));
|
||||
$expression = trim((string) ($config['cron_expression'] ?? ''));
|
||||
$isLocked = $this->isLockActive($state, $nowUtc->getTimestamp());
|
||||
$parseError = null;
|
||||
$previousDueUtc = null;
|
||||
$nextDueUtc = null;
|
||||
$isDue = false;
|
||||
foreach ($this->jobConfigs($definition, $settings) as $entryIndex => $config) {
|
||||
$stateKey = $this->stateKey($name, $entryIndex);
|
||||
$state = $states[$stateKey] ?? [];
|
||||
$timezone = $this->safeTimezone((string) ($config['timezone'] ?? 'UTC'));
|
||||
$expression = trim((string) ($config['cron_expression'] ?? ''));
|
||||
$isLocked = $this->isLockActive($state, $nowUtc->getTimestamp());
|
||||
$parseError = null;
|
||||
$previousDueUtc = null;
|
||||
$nextDueUtc = null;
|
||||
$isDue = false;
|
||||
|
||||
try {
|
||||
$cron = CronExpression::parse($expression);
|
||||
$previousDueUtc = $cron->previousRun($nowUtc, $timezone);
|
||||
$lastScheduledFor = $this->parseUtc((string) ($state['last_scheduled_for'] ?? ''));
|
||||
$isDue = !empty($config['enabled'])
|
||||
&& !$isLocked
|
||||
&& $previousDueUtc instanceof DateTimeImmutable
|
||||
&& ($lastScheduledFor === null || $previousDueUtc > $lastScheduledFor);
|
||||
$nextDueUtc = $isDue
|
||||
? $previousDueUtc
|
||||
: $cron->nextRun($nowUtc, $timezone);
|
||||
} catch (\Throwable $exception) {
|
||||
$parseError = $exception->getMessage();
|
||||
try {
|
||||
$cron = CronExpression::parse($expression);
|
||||
$previousDueUtc = $cron->previousRun($nowUtc, $timezone);
|
||||
$lastScheduledFor = $this->parseUtc((string) ($state['last_scheduled_for'] ?? ''));
|
||||
$isDue = !empty($config['enabled'])
|
||||
&& !$isLocked
|
||||
&& $previousDueUtc instanceof DateTimeImmutable
|
||||
&& ($lastScheduledFor === null || $previousDueUtc > $lastScheduledFor);
|
||||
$nextDueUtc = $isDue
|
||||
? $previousDueUtc
|
||||
: $cron->nextRun($nowUtc, $timezone);
|
||||
} catch (\Throwable $exception) {
|
||||
$parseError = $exception->getMessage();
|
||||
}
|
||||
|
||||
$statuses[] = $definition + [
|
||||
'job_name' => $name,
|
||||
'entry_index' => $entryIndex,
|
||||
'state_key' => $stateKey,
|
||||
'config' => $config,
|
||||
'state' => $state,
|
||||
'enabled' => !empty($config['enabled']),
|
||||
'cron_expression' => $expression,
|
||||
'timezone' => $timezone->getName(),
|
||||
'is_due' => $isDue,
|
||||
'is_locked' => $isLocked,
|
||||
'parse_error' => $parseError,
|
||||
'previous_due_at' => $previousDueUtc?->format('Y-m-d H:i:s'),
|
||||
'next_due_at' => $nextDueUtc?->format('Y-m-d H:i:s'),
|
||||
'previous_due_at_local' => $previousDueUtc?->setTimezone($timezone)->format('Y-m-d H:i:s'),
|
||||
'next_due_at_local' => $nextDueUtc?->setTimezone($timezone)->format('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
$statuses[] = $definition + [
|
||||
'config' => $config,
|
||||
'state' => $state,
|
||||
'enabled' => !empty($config['enabled']),
|
||||
'cron_expression' => $expression,
|
||||
'timezone' => $timezone->getName(),
|
||||
'is_due' => $isDue,
|
||||
'is_locked' => $isLocked,
|
||||
'parse_error' => $parseError,
|
||||
'previous_due_at' => $previousDueUtc?->format('Y-m-d H:i:s'),
|
||||
'next_due_at' => $nextDueUtc?->format('Y-m-d H:i:s'),
|
||||
'previous_due_at_local' => $previousDueUtc?->setTimezone($timezone)->format('Y-m-d H:i:s'),
|
||||
'next_due_at_local' => $nextDueUtc?->setTimezone($timezone)->format('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
return $statuses;
|
||||
@@ -115,14 +121,15 @@ final class ModuleCronScheduler
|
||||
|
||||
if (!$this->modules->hasFunction($moduleName, (string) $task['callback'])) {
|
||||
$results[] = [
|
||||
'task' => $task['name'],
|
||||
'task' => $task['job_name'],
|
||||
'entry_index' => $task['entry_index'],
|
||||
'ok' => false,
|
||||
'message' => 'Callback nicht registriert.',
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->acquireLock($moduleName, (string) $task['name'], (int) $task['lock_minutes'])) {
|
||||
if (!$this->acquireLock($moduleName, (string) $task['state_key'], (int) $task['lock_minutes'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -130,7 +137,7 @@ final class ModuleCronScheduler
|
||||
$scheduledForUtc = trim((string) ($task['previous_due_at'] ?? ''));
|
||||
$timezone = $this->safeTimezone((string) ($task['timezone'] ?? 'UTC'));
|
||||
|
||||
$this->persistState($moduleName, (string) $task['name'], [
|
||||
$this->persistState($moduleName, (string) $task['state_key'], [
|
||||
'last_started_at' => $startedAt,
|
||||
'last_status' => 'running',
|
||||
'last_message' => 'Cron-Lauf gestartet.',
|
||||
@@ -163,16 +170,17 @@ final class ModuleCronScheduler
|
||||
if ($ok && !$skipped) {
|
||||
$payload['last_success_at'] = $finishedAt;
|
||||
}
|
||||
$this->persistState($moduleName, (string) $task['name'], $payload);
|
||||
$this->persistState($moduleName, (string) $task['state_key'], $payload);
|
||||
|
||||
$results[] = [
|
||||
'task' => $task['name'],
|
||||
'task' => $task['job_name'],
|
||||
'entry_index' => $task['entry_index'],
|
||||
'ok' => $ok,
|
||||
'message' => $message,
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
$finishedAt = gmdate('Y-m-d H:i:s');
|
||||
$this->persistState($moduleName, (string) $task['name'], [
|
||||
$this->persistState($moduleName, (string) $task['state_key'], [
|
||||
'last_finished_at' => $finishedAt,
|
||||
'last_status' => 'error',
|
||||
'last_message' => $e->getMessage(),
|
||||
@@ -180,7 +188,8 @@ final class ModuleCronScheduler
|
||||
'last_scheduled_for' => $scheduledForUtc !== '' ? $scheduledForUtc : null,
|
||||
]);
|
||||
$results[] = [
|
||||
'task' => $task['name'],
|
||||
'task' => $task['job_name'],
|
||||
'entry_index' => $task['entry_index'],
|
||||
'ok' => false,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
@@ -190,19 +199,64 @@ final class ModuleCronScheduler
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function jobConfig(array $definition, array $settings): array
|
||||
private function jobConfigs(array $definition, array $settings): array
|
||||
{
|
||||
$jobs = is_array($settings['cron_jobs'] ?? null) ? $settings['cron_jobs'] : [];
|
||||
$job = is_array($jobs[$definition['name']] ?? null) ? $jobs[$definition['name']] : [];
|
||||
$jobs = is_array($settings['scheduler_jobs'] ?? null)
|
||||
? $settings['scheduler_jobs']
|
||||
: (is_array($settings['cron_jobs'] ?? null) ? $settings['cron_jobs'] : []);
|
||||
$jobExists = array_key_exists($definition['name'], $jobs) && is_array($jobs[$definition['name']] ?? null);
|
||||
$job = $jobExists ? $jobs[$definition['name']] : [];
|
||||
$timezoneSetting = trim((string) ($definition['timezone_setting'] ?? ''));
|
||||
$fallbackTimezone = $timezoneSetting !== '' ? trim((string) ($settings[$timezoneSetting] ?? '')) : '';
|
||||
|
||||
return [
|
||||
'enabled' => array_key_exists('enabled', $job) ? $this->settingBool($job['enabled'], (bool) $definition['default_enabled']) : (bool) $definition['default_enabled'],
|
||||
'cron_expression' => trim((string) ($job['cron_expression'] ?? $definition['default_cron'] ?? '')),
|
||||
'timezone' => trim((string) ($job['timezone'] ?? ($fallbackTimezone !== '' ? $fallbackTimezone : $definition['default_timezone']))),
|
||||
'builder' => is_array($job['builder'] ?? null) ? $job['builder'] : [],
|
||||
$defaultEntry = [
|
||||
'enabled' => (bool) $definition['default_enabled'],
|
||||
'cron_expression' => trim((string) ($definition['default_cron'] ?? '0 * * * *')),
|
||||
'timezone' => trim((string) ($fallbackTimezone !== '' ? $fallbackTimezone : ($definition['default_timezone'] ?? 'UTC'))),
|
||||
'builder' => [],
|
||||
];
|
||||
|
||||
$entries = is_array($job['entries'] ?? null) ? $job['entries'] : [];
|
||||
if (!$jobExists && $entries === []) {
|
||||
$legacyEntry = [];
|
||||
foreach (['enabled', 'cron_expression', 'timezone', 'builder'] as $field) {
|
||||
if (array_key_exists($field, $job)) {
|
||||
$legacyEntry[$field] = $job[$field];
|
||||
}
|
||||
}
|
||||
$entries = [$legacyEntry !== [] ? $legacyEntry : $defaultEntry];
|
||||
}
|
||||
|
||||
if ($jobExists && $entries === [] && ($definition['mode'] ?? 'single') === 'multi') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach (array_values($entries) as $entry) {
|
||||
if (!is_array($entry)) {
|
||||
continue;
|
||||
}
|
||||
$result[] = [
|
||||
'enabled' => array_key_exists('enabled', $entry) ? $this->settingBool($entry['enabled'], (bool) $defaultEntry['enabled']) : (bool) $defaultEntry['enabled'],
|
||||
'cron_expression' => trim((string) ($entry['cron_expression'] ?? $defaultEntry['cron_expression'])),
|
||||
'timezone' => trim((string) ($entry['timezone'] ?? $defaultEntry['timezone'])),
|
||||
'builder' => is_array($entry['builder'] ?? null) ? $entry['builder'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
if ($result === []) {
|
||||
$result[] = $defaultEntry;
|
||||
}
|
||||
|
||||
if (($definition['mode'] ?? 'single') !== 'multi') {
|
||||
return [0 => $result[0]];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function stateKey(string $jobName, int $entryIndex): string
|
||||
{
|
||||
return $jobName . '#' . $entryIndex;
|
||||
}
|
||||
|
||||
private function fetchStates(string $moduleName): array
|
||||
|
||||
@@ -307,6 +307,7 @@ final class ModuleManager
|
||||
'description' => $data['description'] ?? '',
|
||||
'setup' => $data['setup'] ?? [],
|
||||
'interval_tasks' => $data['interval_tasks'] ?? [],
|
||||
'scheduler_jobs' => $data['scheduler_jobs'] ?? ($data['cron_tasks'] ?? []),
|
||||
'cron_tasks' => $data['cron_tasks'] ?? [],
|
||||
'menu' => $data['menu'] ?? [],
|
||||
'sidebar' => $data['sidebar'] ?? [],
|
||||
|
||||
Reference in New Issue
Block a user