- Add icanhazstring/composer-unused as dev dependency for dependency hygiene checks - Add German documentation (docs/) covering architecture, conventions, workflows, and developer checklists - Add API layer (ApiAuth, ApiBootstrap, ApiResponse), audit, scheduler, custom fields, and SSO services - Add Microsoft OIDC SSO, API token management, and user lifecycle features - Add swagger-ui vendor integration and OpenAPI spec - Add production Docker setup and bin/ scripts - Update composer dependencies, config, templates, and frontend assets throughout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.2 KiB
Geplante Aufgaben
Letzte Aktualisierung: 2026-02-21
Ziel
Zentrale Verwaltung geplanter Aufgaben im Admin-Bereich.
- Ein zentraler Runner (
bin/scheduler-run.php) läuft minütlich.- Produktion typischerweise per Cron.
- Lokale Docker-Umgebung über den Compose-Service
scheduler.
- Die konkreten Jobs sind Whitelist-basiert (kein frei ausführbarer Bash-Text aus DB).
- V1 startet mit
user_lifecycle_run.
Tabellen
scheduled_jobs- Konfiguration pro Job (enabled, schedule, timezone, next/last run, letzter Fehler).
scheduled_job_runs- Historie einzelner Runs (trigger, status, Dauer, error/result summary).
scheduler_runtime_status- Einzeilige Runtime-Health des globalen Runners (Heartbeat, letzter Runner-Status, letzter Fehlercode).
Run-Log-Retention:
- 90 Tage (Purge in Admin verfügbar).
Rechte
jobs.view: Bereich ansehenjobs.manage: Job-Konfiguration speichernjobs.run_now: Job manuell ausführen
Purge der Run-Logs bleibt unter settings.update.
Betriebsmodell
Cron-Eintrag (Beispiel):
* * * * * docker compose exec php php bin/scheduler-run.php
Der Runner:
- sichert sich globalen Lock (
GET_LOCK('scheduled_jobs_runner', 0)) – nur ein Runner aktiv - holt globale Due-Jobs (
enabled=1,next_run_at <= now) - führt Jobs kontrolliert aus
- schreibt Heartbeat in
scheduler_runtime_status - schreibt Run-Log und aktualisiert Job-Status
- berechnet
next_run_atneu - gibt den Lock frei
Runner-Heartbeat
scheduler_runtime_statusenthält genau eine Zeile (id = 1).- Jeder Aufruf von
bin/scheduler-run.phpaktualisiert diese Zeile:last_heartbeat_atlast_result(ok,lock_not_acquired,unexpected_error)last_error_code(optional)
- Dadurch wächst die Tabelle nicht mit der Zeit; sie ist ein laufendes Statusfenster.
Verwendung in Admin/Stats:
- Kachel Cron runner active ist
Aktiv, wennlast_heartbeat_atjünger als 3 Minuten ist. - Ist der Heartbeat älter oder fehlt, zeigt die Kachel
Inaktiv. - Manuelle Runs aus der UI (
Run now) zählen nicht als Cron-Liveness.
Scheduling (V1)
Unterstützte Typen:
hourly(Intervall 1..24)daily(Intervall 1..365 + Uhrzeit)weekly(Intervall 1..52 + Uhrzeit + Wochentage)
catchup_once:
- Wenn ein Lauf verpasst wurde, wird genau ein Run nachgeholt.
- Danach wird normal in die Zukunft weitergeplant (kein Backlog-Sturm).
Neuen Job registrieren (Entwickler-Guide)
Architektur-überblick
Jeder Job ist eine eigene Handler-Klasse, die ScheduledJobHandlerInterface implementiert.
Die Registry ist eine reine Map von job_key zu Handler-Klasse – sie enthält keine
job-spezifische Logik.
lib/Service/Scheduler/
Handler/
ScheduledJobHandlerInterface.php ← Vertrag für alle Handler
UserLifecycleJobHandler.php ← Referenz-Implementierung
ScheduledJobRegistry.php ← Handler-Map (einzige Datei die bei neuen Jobs angefasst wird)
Schritt-für-Schritt
1. Handler-Klasse erstellen
Datei: lib/Service/Scheduler/Handler/MeinJobHandler.php
<?php
namespace MintyPHP\Service\Scheduler\Handler;
use MintyPHP\Service\MeinBereich\MeinService;
class MeinJobHandler implements ScheduledJobHandlerInterface
{
public static function definition(): array
{
return [
'label' => 'Mein Job',
'description' => 'Kurze Beschreibung was der Job macht',
'default_enabled' => 1,
'default_timezone' => defined('APP_TIMEZONE') ? (string) APP_TIMEZONE : 'UTC',
'default_schedule_type' => 'daily',
'default_schedule_interval' => 1,
'default_schedule_time' => '03:00',
'default_schedule_weekdays_csv' => null,
'default_catchup_once' => 1,
'allowed_schedule_types' => ['daily', 'weekly'],
];
}
public static function execute(?int $actorUserId): array
{
$result = MeinService::run($actorUserId);
if (!($result['ok'] ?? false)) {
$errorCode = (string) ($result['error'] ?? 'job_failed');
$status = $errorCode === 'lock_not_acquired' ? 'skipped' : 'failed';
return [
'status' => $status,
'error_code' => $errorCode,
'error_message' => null,
'result' => [],
];
}
return [
'status' => 'success',
'error_code' => null,
'error_message' => null,
'result' => [
'processed_count' => (int) ($result['processed_count'] ?? 0),
'duration_ms' => (int) ($result['duration_ms'] ?? 0),
],
];
}
}
2. Job in der Registry eintragen
Datei: lib/Service/Scheduler/ScheduledJobRegistry.php
// Konstante hinzufügen:
public const MEIN_JOB = 'mein_job';
// In handlers() eine Zeile hinzufügen:
private static function handlers(): array
{
return [
self::USER_LIFECYCLE_RUN => UserLifecycleJobHandler::class,
self::MEIN_JOB => MeinJobHandler::class, // ← neu
];
}
3. Fertig.
Keine anderen Dateien müssen angefasst werden. ensureSystemJobs() legt den Job beim
nächsten Scheduler-Aufruf automatisch in der Datenbank an.
Regeln für Handler
definition()
- Kein
job_key-Feld – der Array-Key in der Registry-Map ist der Job-Key. default_timezone: Am bestenAPP_TIMEZONE-Konstante verwenden, Fallback'UTC'.allowed_schedule_types: Nur Typen erlauben, die der Job sinnvoll unterstützt. Ein Job der stundlich keinen Sinn ergibt, solltehourlynicht listen.
execute()
- Muss immer ein Array mit
status,error_code,error_message,resultzurückgeben. statusdarf nur'success','failed'oder'skipped'sein.error_messagedarf maximal 255 Zeichen lang sein (VARCHAR-Limit in DB).resultwird als JSON im Run-Log gespeichert – nur JSON-serialisierbare Werte.- Wenn der zugrundeliegende Service einen eigenen Lock hat und
lock_not_acquiredmeldet, sollte der Handlerstatus = 'skipped'zurückgeben (kein echter Fehler).
Namenskonvention
- Dateiname:
MeinJobHandler.php(PascalCase +Handler-Suffix) - Konstante in Registry:
SCREAMING_SNAKE_CASE(z.B.AUDIT_PURGE_RUN) - Job-Key in DB:
snake_case(z.B.audit_purge_run)
Referenz-Implementierung
lib/Service/Scheduler/Handler/UserLifecycleJobHandler.php ist die vollständige
Referenz-Implementierung mit internem Service-Lock, Fehlerbehandlung und
job-spezifischem result-Payload.
Registrierte Jobs
| job_key | Handler-Klasse | Default-Schedule |
|---|---|---|
user_lifecycle_run |
UserLifecycleJobHandler |
täglich 02:15 |
Troubleshooting
lock_not_acquired:- Ein anderer Runner läuft bereits.
- Job bleibt auf
running:- stale-running Schutz erlaubt nach 2 Stunden wieder neue Runs (Schwellwert in
ScheduledJobRepository::markRunning).
- stale-running Schutz erlaubt nach 2 Stunden wieder neue Runs (Schwellwert in
- Kein fälliger Job:
next_run_at,enabled,timezoneund schedule Konfiguration prüfen.