instances added god may help
This commit is contained in:
@@ -8,7 +8,7 @@ interface ScheduledJobHandlerInterface
|
||||
* Returns the job definition (metadata + schedule defaults).
|
||||
*
|
||||
* The job_key is NOT included here – it is provided as the key in the
|
||||
* registry handler map (ScheduledJobRegistry::handlers()).
|
||||
* registry handler map in ScheduledJobRegistry.
|
||||
*
|
||||
* Required keys:
|
||||
* label (string) Human-readable name shown in admin UI.
|
||||
@@ -22,7 +22,7 @@ interface ScheduledJobHandlerInterface
|
||||
* default_catchup_once (int) 1 = catch up once after downtime (default).
|
||||
* allowed_schedule_types (array) Subset of ['hourly','daily','weekly'].
|
||||
*/
|
||||
public static function definition(): array;
|
||||
public function definition(): array;
|
||||
|
||||
/**
|
||||
* Executes the job and returns a normalized result envelope.
|
||||
@@ -42,5 +42,5 @@ interface ScheduledJobHandlerInterface
|
||||
* error_message: human-readable detail, null on success (max 255 chars)
|
||||
* result: job-specific payload, empty array if nothing to report
|
||||
*/
|
||||
public static function execute(?int $actorUserId): array;
|
||||
public function execute(?int $actorUserId): array;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,11 @@ use MintyPHP\Service\User\UserLifecycleService;
|
||||
|
||||
class UserLifecycleJobHandler implements ScheduledJobHandlerInterface
|
||||
{
|
||||
public static function definition(): array
|
||||
public function __construct(private readonly UserLifecycleService $userLifecycleService)
|
||||
{
|
||||
}
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'label' => 'User lifecycle run',
|
||||
@@ -22,9 +26,9 @@ class UserLifecycleJobHandler implements ScheduledJobHandlerInterface
|
||||
];
|
||||
}
|
||||
|
||||
public static function execute(?int $actorUserId): array
|
||||
public function execute(?int $actorUserId): array
|
||||
{
|
||||
$result = UserLifecycleService::run($actorUserId !== null && $actorUserId > 0 ? $actorUserId : null);
|
||||
$result = $this->userLifecycleService->run($actorUserId !== null && $actorUserId > 0 ? $actorUserId : null);
|
||||
|
||||
if (!($result['ok'] ?? false)) {
|
||||
$errorCode = (string) ($result['error'] ?? 'job_failed');
|
||||
@@ -33,7 +37,7 @@ class UserLifecycleJobHandler implements ScheduledJobHandlerInterface
|
||||
'status' => $status,
|
||||
'error_code' => $errorCode,
|
||||
'error_message' => null,
|
||||
'result' => self::formatResult($result),
|
||||
'result' => $this->formatResult($result),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -41,11 +45,11 @@ class UserLifecycleJobHandler implements ScheduledJobHandlerInterface
|
||||
'status' => 'success',
|
||||
'error_code' => null,
|
||||
'error_message' => null,
|
||||
'result' => self::formatResult($result),
|
||||
'result' => $this->formatResult($result),
|
||||
];
|
||||
}
|
||||
|
||||
private static function formatResult(array $result): array
|
||||
private function formatResult(array $result): array
|
||||
{
|
||||
return [
|
||||
'run_uuid' => (string) ($result['run_uuid'] ?? ''),
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace MintyPHP\Service\Scheduler;
|
||||
|
||||
class ScheduleCalculator
|
||||
{
|
||||
public static function normalizeTimezone(?string $timezone): string
|
||||
public function normalizeTimezone(?string $timezone): string
|
||||
{
|
||||
$timezone = trim((string) $timezone);
|
||||
if ($timezone === '') {
|
||||
@@ -18,15 +18,15 @@ class ScheduleCalculator
|
||||
}
|
||||
}
|
||||
|
||||
public static function normalizeScheduleType(?string $type): string
|
||||
public function normalizeScheduleType(?string $type): string
|
||||
{
|
||||
$type = strtolower(trim((string) $type));
|
||||
return in_array($type, ['hourly', 'daily', 'weekly'], true) ? $type : 'daily';
|
||||
}
|
||||
|
||||
public static function normalizeInterval(string $type, int $value): int
|
||||
public function normalizeInterval(string $type, int $value): int
|
||||
{
|
||||
$type = self::normalizeScheduleType($type);
|
||||
$type = $this->normalizeScheduleType($type);
|
||||
if ($type === 'hourly') {
|
||||
return max(1, min(24, $value));
|
||||
}
|
||||
@@ -36,7 +36,7 @@ class ScheduleCalculator
|
||||
return max(1, min(365, $value));
|
||||
}
|
||||
|
||||
public static function normalizeTime(?string $time): ?string
|
||||
public function normalizeTime(?string $time): ?string
|
||||
{
|
||||
$time = trim((string) $time);
|
||||
if ($time === '') {
|
||||
@@ -53,9 +53,9 @@ class ScheduleCalculator
|
||||
return sprintf('%02d:%02d', $hour, $minute);
|
||||
}
|
||||
|
||||
public static function normalizeWeekdaysCsv(mixed $value): ?string
|
||||
public function normalizeWeekdaysCsv(mixed $value): ?string
|
||||
{
|
||||
$list = self::parseWeekdays($value);
|
||||
$list = $this->parseWeekdays($value);
|
||||
if (!$list) {
|
||||
return null;
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class ScheduleCalculator
|
||||
/**
|
||||
* @return array<int>
|
||||
*/
|
||||
public static function parseWeekdays(mixed $value): array
|
||||
public function parseWeekdays(mixed $value): array
|
||||
{
|
||||
$raw = [];
|
||||
if (is_array($value)) {
|
||||
@@ -104,12 +104,12 @@ class ScheduleCalculator
|
||||
* Returns null if the schedule is misconfigured (e.g. missing schedule_time for daily/weekly)
|
||||
* or if no valid next run could be found within the search window.
|
||||
*/
|
||||
public static function calculateNextRunUtc(array $job, ?\DateTimeImmutable $referenceUtc = null): ?\DateTimeImmutable
|
||||
public function calculateNextRunUtc(array $job, ?\DateTimeImmutable $referenceUtc = null): ?\DateTimeImmutable
|
||||
{
|
||||
$referenceUtc = $referenceUtc ?? new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
|
||||
$type = self::normalizeScheduleType((string) ($job['schedule_type'] ?? 'daily'));
|
||||
$interval = self::normalizeInterval($type, (int) ($job['schedule_interval'] ?? 1));
|
||||
$timezone = new \DateTimeZone(self::normalizeTimezone((string) ($job['timezone'] ?? '')));
|
||||
$type = $this->normalizeScheduleType((string) ($job['schedule_type'] ?? 'daily'));
|
||||
$interval = $this->normalizeInterval($type, (int) ($job['schedule_interval'] ?? 1));
|
||||
$timezone = new \DateTimeZone($this->normalizeTimezone((string) ($job['timezone'] ?? '')));
|
||||
$localReference = $referenceUtc->setTimezone($timezone);
|
||||
|
||||
if ($type === 'hourly') {
|
||||
@@ -120,7 +120,7 @@ class ScheduleCalculator
|
||||
return $candidate->setTimezone(new \DateTimeZone('UTC'));
|
||||
}
|
||||
|
||||
$time = self::normalizeTime((string) ($job['schedule_time'] ?? ''));
|
||||
$time = $this->normalizeTime((string) ($job['schedule_time'] ?? ''));
|
||||
if ($time === null) {
|
||||
return null;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class ScheduleCalculator
|
||||
// week interval. The search window is 1110 days (~3 years), which comfortably covers
|
||||
// the maximum weekly interval of 52 weeks × 7 days/week = 364 days, with margin for
|
||||
// weekday alignment and DST edge cases.
|
||||
$weekdays = self::parseWeekdays((string) ($job['schedule_weekdays_csv'] ?? ''));
|
||||
$weekdays = $this->parseWeekdays((string) ($job['schedule_weekdays_csv'] ?? ''));
|
||||
if (!$weekdays) {
|
||||
$weekdays = [1];
|
||||
}
|
||||
|
||||
@@ -4,39 +4,42 @@ namespace MintyPHP\Service\Scheduler;
|
||||
|
||||
use MintyPHP\Service\Scheduler\Handler\ScheduledJobHandlerInterface;
|
||||
use MintyPHP\Service\Scheduler\Handler\UserLifecycleJobHandler;
|
||||
use MintyPHP\Service\User\UserLifecycleService;
|
||||
|
||||
class ScheduledJobRegistry
|
||||
{
|
||||
public const USER_LIFECYCLE_RUN = 'user_lifecycle_run';
|
||||
|
||||
/**
|
||||
* Maps job_key => handler class name (must implement ScheduledJobHandlerInterface).
|
||||
* Maps job_key => handler instance (must implement ScheduledJobHandlerInterface).
|
||||
*
|
||||
* To register a new job:
|
||||
* 1. Create lib/Service/Scheduler/Handler/YourJobHandler.php implementing the interface.
|
||||
* 2. Add a public const for the job key above.
|
||||
* 3. Add one line here: self::YOUR_JOB_KEY => YourJobHandler::class,
|
||||
* 3. Add one line in __construct(): self::YOUR_JOB_KEY => new YourJobHandler(...),
|
||||
*
|
||||
* No other file needs to be changed.
|
||||
*
|
||||
* @return array<string, class-string<ScheduledJobHandlerInterface>>
|
||||
* @var array<string, ScheduledJobHandlerInterface>
|
||||
*/
|
||||
private static function handlers(): array
|
||||
private array $handlers;
|
||||
|
||||
public function __construct(UserLifecycleService $userLifecycleService)
|
||||
{
|
||||
return [
|
||||
self::USER_LIFECYCLE_RUN => UserLifecycleJobHandler::class,
|
||||
$this->handlers = [
|
||||
self::USER_LIFECYCLE_RUN => new UserLifecycleJobHandler($userLifecycleService),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all job definitions keyed by job_key.
|
||||
* Consumed by ScheduledJobService::ensureSystemJobs() to sync registry with the database.
|
||||
* Consumed by ScheduledJobService->ensureSystemJobs() to sync registry with the database.
|
||||
*/
|
||||
public static function definitions(): array
|
||||
public function definitions(): array
|
||||
{
|
||||
$result = [];
|
||||
foreach (self::handlers() as $jobKey => $handlerClass) {
|
||||
$result[$jobKey] = $handlerClass::definition();
|
||||
foreach ($this->handlers as $jobKey => $handler) {
|
||||
$result[$jobKey] = $handler->definition();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
@@ -44,14 +47,13 @@ class ScheduledJobRegistry
|
||||
/**
|
||||
* Returns the definition for a single job key, or null if not registered.
|
||||
*/
|
||||
public static function get(string $jobKey): ?array
|
||||
public function get(string $jobKey): ?array
|
||||
{
|
||||
$jobKey = trim($jobKey);
|
||||
if ($jobKey === '') {
|
||||
return null;
|
||||
}
|
||||
$handlers = self::handlers();
|
||||
return isset($handlers[$jobKey]) ? $handlers[$jobKey]::definition() : null;
|
||||
return isset($this->handlers[$jobKey]) ? $this->handlers[$jobKey]->definition() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,10 +65,9 @@ class ScheduledJobRegistry
|
||||
* @param int|null $actorUserId Null for scheduler-triggered runs; user ID for manual runs.
|
||||
* @return array{status:string,error_code:?string,error_message:?string,result:array}
|
||||
*/
|
||||
public static function execute(string $jobKey, ?int $actorUserId): array
|
||||
public function execute(string $jobKey, ?int $actorUserId): array
|
||||
{
|
||||
$handlers = self::handlers();
|
||||
if (!isset($handlers[$jobKey])) {
|
||||
if (!isset($this->handlers[$jobKey])) {
|
||||
return [
|
||||
'status' => 'failed',
|
||||
'error_code' => 'job_not_supported',
|
||||
@@ -74,6 +75,6 @@ class ScheduledJobRegistry
|
||||
'result' => [],
|
||||
];
|
||||
}
|
||||
return $handlers[$jobKey]::execute($actorUserId);
|
||||
return $this->handlers[$jobKey]->execute($actorUserId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,59 +9,67 @@ class ScheduledJobService
|
||||
{
|
||||
private const RUN_RETENTION_DAYS = 90;
|
||||
|
||||
public static function ensureSystemJobs(): void
|
||||
public function __construct(
|
||||
private readonly ScheduledJobRepository $scheduledJobRepository,
|
||||
private readonly ScheduledJobRunRepository $scheduledJobRunRepository,
|
||||
private readonly ScheduledJobRegistry $scheduledJobRegistry,
|
||||
private readonly ScheduleCalculator $scheduleCalculator
|
||||
) {
|
||||
}
|
||||
|
||||
public function ensureSystemJobs(): void
|
||||
{
|
||||
$definitions = ScheduledJobRegistry::definitions();
|
||||
$definitions = $this->scheduledJobRegistry->definitions();
|
||||
$nowUtc = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
|
||||
|
||||
foreach ($definitions as $jobKey => $definition) {
|
||||
$existing = ScheduledJobRepository::findByKey($jobKey);
|
||||
$job = self::buildDefaultJob($definition, $jobKey, $nowUtc);
|
||||
$existing = $this->scheduledJobRepository->findByKey($jobKey);
|
||||
$job = $this->buildDefaultJob($definition, $jobKey, $nowUtc);
|
||||
|
||||
if (!$existing) {
|
||||
ScheduledJobRepository::create($job);
|
||||
$this->scheduledJobRepository->create($job);
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalized = self::normalizeStoredJob($existing, $definition);
|
||||
if (!self::jobsDiffer($existing, $normalized)) {
|
||||
$normalized = $this->normalizeStoredJob($existing, $definition);
|
||||
if (!$this->jobsDiffer($existing, $normalized)) {
|
||||
continue;
|
||||
}
|
||||
ScheduledJobRepository::updateJobMeta((int) $existing['id'], $normalized);
|
||||
$this->scheduledJobRepository->updateJobMeta((int) $existing['id'], $normalized);
|
||||
}
|
||||
}
|
||||
|
||||
public static function listPaged(array $filters): array
|
||||
public function listPaged(array $filters): array
|
||||
{
|
||||
self::ensureSystemJobs();
|
||||
return ScheduledJobRepository::listPaged($filters);
|
||||
$this->ensureSystemJobs();
|
||||
return $this->scheduledJobRepository->listPaged($filters);
|
||||
}
|
||||
|
||||
public static function find(int $id): ?array
|
||||
public function find(int $id): ?array
|
||||
{
|
||||
self::ensureSystemJobs();
|
||||
return ScheduledJobRepository::find($id);
|
||||
$this->ensureSystemJobs();
|
||||
return $this->scheduledJobRepository->find($id);
|
||||
}
|
||||
|
||||
public static function updateFromAdmin(int $id, array $input): array
|
||||
public function updateFromAdmin(int $id, array $input): array
|
||||
{
|
||||
$job = self::find($id);
|
||||
$job = $this->find($id);
|
||||
if (!$job) {
|
||||
return ['ok' => false, 'errors' => [t('Scheduled job not found')]];
|
||||
return ['ok' => false, 'errors' => [$this->translate('Scheduled job not found')]];
|
||||
}
|
||||
|
||||
$definition = ScheduledJobRegistry::get((string) ($job['job_key'] ?? ''));
|
||||
$definition = $this->scheduledJobRegistry->get((string) ($job['job_key'] ?? ''));
|
||||
if (!$definition) {
|
||||
return ['ok' => false, 'errors' => [t('Scheduled job is not registered')]];
|
||||
return ['ok' => false, 'errors' => [$this->translate('Scheduled job is not registered')]];
|
||||
}
|
||||
|
||||
$form = [
|
||||
'enabled' => isset($input['enabled']) ? 1 : 0,
|
||||
'timezone' => ScheduleCalculator::normalizeTimezone((string) ($input['timezone'] ?? ($job['timezone'] ?? ''))),
|
||||
'schedule_type' => ScheduleCalculator::normalizeScheduleType((string) ($input['schedule_type'] ?? ($job['schedule_type'] ?? 'daily'))),
|
||||
'timezone' => $this->scheduleCalculator->normalizeTimezone((string) ($input['timezone'] ?? ($job['timezone'] ?? ''))),
|
||||
'schedule_type' => $this->scheduleCalculator->normalizeScheduleType((string) ($input['schedule_type'] ?? ($job['schedule_type'] ?? 'daily'))),
|
||||
'schedule_interval' => (int) ($input['schedule_interval'] ?? ($job['schedule_interval'] ?? 1)),
|
||||
'schedule_time' => ScheduleCalculator::normalizeTime((string) ($input['schedule_time'] ?? ($job['schedule_time'] ?? ''))),
|
||||
'schedule_weekdays_csv' => ScheduleCalculator::normalizeWeekdaysCsv(
|
||||
'schedule_time' => $this->scheduleCalculator->normalizeTime((string) ($input['schedule_time'] ?? ($job['schedule_time'] ?? ''))),
|
||||
'schedule_weekdays_csv' => $this->scheduleCalculator->normalizeWeekdaysCsv(
|
||||
array_key_exists('schedule_weekdays', $input)
|
||||
? $input['schedule_weekdays']
|
||||
: ($job['schedule_weekdays_csv'] ?? '')
|
||||
@@ -69,18 +77,18 @@ class ScheduledJobService
|
||||
'catchup_once' => isset($input['catchup_once']) ? 1 : 0,
|
||||
];
|
||||
|
||||
$errors = self::validateForm($form, $definition);
|
||||
$errors = $this->validateForm($form, $definition);
|
||||
if ($errors) {
|
||||
return ['ok' => false, 'errors' => $errors, 'form' => $form, 'job' => $job];
|
||||
}
|
||||
|
||||
$nextRunAt = null;
|
||||
if ((int) $form['enabled'] === 1) {
|
||||
$nextRun = ScheduleCalculator::calculateNextRunUtc($form, new \DateTimeImmutable('now', new \DateTimeZone('UTC')));
|
||||
$nextRun = $this->scheduleCalculator->calculateNextRunUtc($form, new \DateTimeImmutable('now', new \DateTimeZone('UTC')));
|
||||
$nextRunAt = $nextRun ? $nextRun->format('Y-m-d H:i:s') : null;
|
||||
}
|
||||
|
||||
$updated = ScheduledJobRepository::updateJobMeta((int) $job['id'], [
|
||||
$updated = $this->scheduledJobRepository->updateJobMeta((int) $job['id'], [
|
||||
'label' => (string) ($definition['label'] ?? (string) ($job['label'] ?? '')),
|
||||
'description' => (string) ($definition['description'] ?? (string) ($job['description'] ?? '')),
|
||||
'enabled' => (int) $form['enabled'],
|
||||
@@ -94,95 +102,95 @@ class ScheduledJobService
|
||||
]);
|
||||
|
||||
if (!$updated) {
|
||||
return ['ok' => false, 'errors' => [t('Scheduled job could not be saved')], 'form' => $form, 'job' => $job];
|
||||
return ['ok' => false, 'errors' => [$this->translate('Scheduled job could not be saved')], 'form' => $form, 'job' => $job];
|
||||
}
|
||||
|
||||
$fresh = self::find((int) $job['id']);
|
||||
$fresh = $this->find((int) $job['id']);
|
||||
return ['ok' => true, 'job' => $fresh, 'form' => $form];
|
||||
}
|
||||
|
||||
public static function listRunsByJobId(int $jobId, array $filters): array
|
||||
public function listRunsByJobId(int $jobId, array $filters): array
|
||||
{
|
||||
return ScheduledJobRunRepository::listPagedByJobId($jobId, $filters);
|
||||
return $this->scheduledJobRunRepository->listPagedByJobId($jobId, $filters);
|
||||
}
|
||||
|
||||
public static function purgeRunsExpired(): int
|
||||
public function purgeRunsExpired(): int
|
||||
{
|
||||
return ScheduledJobRunRepository::purgeOlderThanDays(self::RUN_RETENTION_DAYS);
|
||||
return $this->scheduledJobRunRepository->purgeOlderThanDays(self::RUN_RETENTION_DAYS);
|
||||
}
|
||||
|
||||
public static function scheduleSummary(array $job): string
|
||||
public function scheduleSummary(array $job): string
|
||||
{
|
||||
$type = ScheduleCalculator::normalizeScheduleType((string) ($job['schedule_type'] ?? 'daily'));
|
||||
$type = $this->scheduleCalculator->normalizeScheduleType((string) ($job['schedule_type'] ?? 'daily'));
|
||||
$interval = (int) ($job['schedule_interval'] ?? 1);
|
||||
$time = (string) ($job['schedule_time'] ?? '');
|
||||
|
||||
if ($type === 'hourly') {
|
||||
return sprintf(t('Every %d hour(s)'), max(1, $interval));
|
||||
return sprintf($this->translate('Every %d hour(s)'), max(1, $interval));
|
||||
}
|
||||
if ($type === 'daily') {
|
||||
return sprintf(t('Every %d day(s) at %s'), max(1, $interval), $time !== '' ? $time : '--:--');
|
||||
return sprintf($this->translate('Every %d day(s) at %s'), max(1, $interval), $time !== '' ? $time : '--:--');
|
||||
}
|
||||
|
||||
$weekdays = ScheduleCalculator::parseWeekdays((string) ($job['schedule_weekdays_csv'] ?? ''));
|
||||
$weekdays = $this->scheduleCalculator->parseWeekdays((string) ($job['schedule_weekdays_csv'] ?? ''));
|
||||
$labels = [];
|
||||
foreach ($weekdays as $day) {
|
||||
$labels[] = self::weekdayLabel($day);
|
||||
$labels[] = $this->weekdayLabel($day);
|
||||
}
|
||||
$weekdayText = $labels ? implode(', ', $labels) : t('No weekdays');
|
||||
$weekdayText = $labels ? implode(', ', $labels) : $this->translate('No weekdays');
|
||||
return sprintf(
|
||||
t('Every %d week(s) on %s at %s'),
|
||||
$this->translate('Every %d week(s) on %s at %s'),
|
||||
max(1, $interval),
|
||||
$weekdayText,
|
||||
$time !== '' ? $time : '--:--'
|
||||
);
|
||||
}
|
||||
|
||||
public static function weekdaysFromCsv(?string $csv): array
|
||||
public function weekdaysFromCsv(?string $csv): array
|
||||
{
|
||||
return ScheduleCalculator::parseWeekdays((string) ($csv ?? ''));
|
||||
return $this->scheduleCalculator->parseWeekdays((string) ($csv ?? ''));
|
||||
}
|
||||
|
||||
private static function validateForm(array $form, array $definition): array
|
||||
private function validateForm(array $form, array $definition): array
|
||||
{
|
||||
$errors = [];
|
||||
$allowedTypes = $definition['allowed_schedule_types'] ?? ['hourly', 'daily', 'weekly'];
|
||||
$type = (string) ($form['schedule_type'] ?? 'daily');
|
||||
if (!in_array($type, $allowedTypes, true)) {
|
||||
$errors[] = t('Schedule type is invalid');
|
||||
$errors[] = $this->translate('Schedule type is invalid');
|
||||
return $errors;
|
||||
}
|
||||
|
||||
$interval = (int) ($form['schedule_interval'] ?? 0);
|
||||
if ($type === 'hourly' && ($interval < 1 || $interval > 24)) {
|
||||
$errors[] = t('Hourly interval must be between 1 and 24');
|
||||
$errors[] = $this->translate('Hourly interval must be between 1 and 24');
|
||||
} elseif ($type === 'daily' && ($interval < 1 || $interval > 365)) {
|
||||
$errors[] = t('Daily interval must be between 1 and 365');
|
||||
$errors[] = $this->translate('Daily interval must be between 1 and 365');
|
||||
} elseif ($type === 'weekly' && ($interval < 1 || $interval > 52)) {
|
||||
$errors[] = t('Weekly interval must be between 1 and 52');
|
||||
$errors[] = $this->translate('Weekly interval must be between 1 and 52');
|
||||
}
|
||||
|
||||
$timezone = trim((string) ($form['timezone'] ?? ''));
|
||||
try {
|
||||
new \DateTimeZone($timezone);
|
||||
} catch (\Throwable $exception) {
|
||||
$errors[] = t('Timezone is invalid');
|
||||
$errors[] = $this->translate('Timezone is invalid');
|
||||
}
|
||||
|
||||
if (in_array($type, ['daily', 'weekly'], true) && $form['schedule_time'] === null) {
|
||||
$errors[] = t('Schedule time is required');
|
||||
$errors[] = $this->translate('Schedule time is required');
|
||||
}
|
||||
if ($type === 'weekly') {
|
||||
$weekdays = ScheduleCalculator::parseWeekdays((string) ($form['schedule_weekdays_csv'] ?? ''));
|
||||
$weekdays = $this->scheduleCalculator->parseWeekdays((string) ($form['schedule_weekdays_csv'] ?? ''));
|
||||
if (!$weekdays) {
|
||||
$errors[] = t('At least one weekday is required for weekly schedule');
|
||||
$errors[] = $this->translate('At least one weekday is required for weekly schedule');
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
private static function buildDefaultJob(array $definition, string $jobKey, \DateTimeImmutable $nowUtc): array
|
||||
private function buildDefaultJob(array $definition, string $jobKey, \DateTimeImmutable $nowUtc): array
|
||||
{
|
||||
$type = (string) ($definition['default_schedule_type'] ?? 'daily');
|
||||
$job = [
|
||||
@@ -190,40 +198,40 @@ class ScheduledJobService
|
||||
'label' => (string) ($definition['label'] ?? $jobKey),
|
||||
'description' => (string) ($definition['description'] ?? ''),
|
||||
'enabled' => (int) ($definition['default_enabled'] ?? 1),
|
||||
'timezone' => ScheduleCalculator::normalizeTimezone((string) ($definition['default_timezone'] ?? '')),
|
||||
'schedule_type' => ScheduleCalculator::normalizeScheduleType($type),
|
||||
'schedule_interval' => ScheduleCalculator::normalizeInterval($type, (int) ($definition['default_schedule_interval'] ?? 1)),
|
||||
'schedule_time' => ScheduleCalculator::normalizeTime((string) ($definition['default_schedule_time'] ?? '')),
|
||||
'schedule_weekdays_csv' => ScheduleCalculator::normalizeWeekdaysCsv($definition['default_schedule_weekdays_csv'] ?? null),
|
||||
'timezone' => $this->scheduleCalculator->normalizeTimezone((string) ($definition['default_timezone'] ?? '')),
|
||||
'schedule_type' => $this->scheduleCalculator->normalizeScheduleType($type),
|
||||
'schedule_interval' => $this->scheduleCalculator->normalizeInterval($type, (int) ($definition['default_schedule_interval'] ?? 1)),
|
||||
'schedule_time' => $this->scheduleCalculator->normalizeTime((string) ($definition['default_schedule_time'] ?? '')),
|
||||
'schedule_weekdays_csv' => $this->scheduleCalculator->normalizeWeekdaysCsv($definition['default_schedule_weekdays_csv'] ?? null),
|
||||
'catchup_once' => (int) ($definition['default_catchup_once'] ?? 1),
|
||||
'next_run_at' => null,
|
||||
];
|
||||
if ((int) $job['enabled'] === 1) {
|
||||
$nextRun = ScheduleCalculator::calculateNextRunUtc($job, $nowUtc);
|
||||
$nextRun = $this->scheduleCalculator->calculateNextRunUtc($job, $nowUtc);
|
||||
$job['next_run_at'] = $nextRun ? $nextRun->format('Y-m-d H:i:s') : null;
|
||||
}
|
||||
return $job;
|
||||
}
|
||||
|
||||
private static function normalizeStoredJob(array $existing, array $definition): array
|
||||
private function normalizeStoredJob(array $existing, array $definition): array
|
||||
{
|
||||
$type = ScheduleCalculator::normalizeScheduleType((string) ($existing['schedule_type'] ?? ($definition['default_schedule_type'] ?? 'daily')));
|
||||
$type = $this->scheduleCalculator->normalizeScheduleType((string) ($existing['schedule_type'] ?? ($definition['default_schedule_type'] ?? 'daily')));
|
||||
$normalized = [
|
||||
'label' => (string) ($definition['label'] ?? (string) ($existing['label'] ?? '')),
|
||||
'description' => (string) ($definition['description'] ?? (string) ($existing['description'] ?? '')),
|
||||
'enabled' => (int) ($existing['enabled'] ?? ($definition['default_enabled'] ?? 1)),
|
||||
'timezone' => ScheduleCalculator::normalizeTimezone((string) ($existing['timezone'] ?? ($definition['default_timezone'] ?? ''))),
|
||||
'timezone' => $this->scheduleCalculator->normalizeTimezone((string) ($existing['timezone'] ?? ($definition['default_timezone'] ?? ''))),
|
||||
'schedule_type' => $type,
|
||||
'schedule_interval' => ScheduleCalculator::normalizeInterval($type, (int) ($existing['schedule_interval'] ?? ($definition['default_schedule_interval'] ?? 1))),
|
||||
'schedule_time' => ScheduleCalculator::normalizeTime((string) ($existing['schedule_time'] ?? ($definition['default_schedule_time'] ?? ''))),
|
||||
'schedule_weekdays_csv' => ScheduleCalculator::normalizeWeekdaysCsv(
|
||||
'schedule_interval' => $this->scheduleCalculator->normalizeInterval($type, (int) ($existing['schedule_interval'] ?? ($definition['default_schedule_interval'] ?? 1))),
|
||||
'schedule_time' => $this->scheduleCalculator->normalizeTime((string) ($existing['schedule_time'] ?? ($definition['default_schedule_time'] ?? ''))),
|
||||
'schedule_weekdays_csv' => $this->scheduleCalculator->normalizeWeekdaysCsv(
|
||||
(string) ($existing['schedule_weekdays_csv'] ?? ($definition['default_schedule_weekdays_csv'] ?? ''))
|
||||
),
|
||||
'catchup_once' => (int) ($existing['catchup_once'] ?? ($definition['default_catchup_once'] ?? 1)),
|
||||
'next_run_at' => (string) ($existing['next_run_at'] ?? ''),
|
||||
];
|
||||
if ($normalized['next_run_at'] === '' && (int) $normalized['enabled'] === 1) {
|
||||
$nextRun = ScheduleCalculator::calculateNextRunUtc($normalized, new \DateTimeImmutable('now', new \DateTimeZone('UTC')));
|
||||
$nextRun = $this->scheduleCalculator->calculateNextRunUtc($normalized, new \DateTimeImmutable('now', new \DateTimeZone('UTC')));
|
||||
$normalized['next_run_at'] = $nextRun ? $nextRun->format('Y-m-d H:i:s') : null;
|
||||
} else {
|
||||
$normalized['next_run_at'] = $normalized['next_run_at'] !== '' ? $normalized['next_run_at'] : null;
|
||||
@@ -231,7 +239,7 @@ class ScheduledJobService
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
private static function jobsDiffer(array $existing, array $normalized): bool
|
||||
private function jobsDiffer(array $existing, array $normalized): bool
|
||||
{
|
||||
$keys = [
|
||||
'label',
|
||||
@@ -255,17 +263,28 @@ class ScheduledJobService
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function weekdayLabel(int $weekday): string
|
||||
private function weekdayLabel(int $weekday): string
|
||||
{
|
||||
return match ($weekday) {
|
||||
1 => t('Mon'),
|
||||
2 => t('Tue'),
|
||||
3 => t('Wed'),
|
||||
4 => t('Thu'),
|
||||
5 => t('Fri'),
|
||||
6 => t('Sat'),
|
||||
7 => t('Sun'),
|
||||
1 => $this->translate('Mon'),
|
||||
2 => $this->translate('Tue'),
|
||||
3 => $this->translate('Wed'),
|
||||
4 => $this->translate('Thu'),
|
||||
5 => $this->translate('Fri'),
|
||||
6 => $this->translate('Sat'),
|
||||
7 => $this->translate('Sun'),
|
||||
default => '-',
|
||||
};
|
||||
}
|
||||
|
||||
private function translate(string $text, mixed ...$args): string
|
||||
{
|
||||
if (function_exists('t')) {
|
||||
return t($text, ...$args);
|
||||
}
|
||||
if ($args === []) {
|
||||
return $text;
|
||||
}
|
||||
return vsprintf($text, array_map(static fn ($arg) => (string) $arg, $args));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,15 +14,25 @@ use MintyPHP\Repository\Support\RepoQuery;
|
||||
* Acquires a MySQL advisory lock before any work to ensure only one runner
|
||||
* process is active at a time (safe for minute-by-minute cron invocations).
|
||||
* Individual jobs are dispatched via ScheduledJobRegistry, and every execution
|
||||
* attempt – including skips and failures – is written to the run log.
|
||||
* attempt - including skips and failures - is written to the run log.
|
||||
*/
|
||||
class SchedulerRunService
|
||||
{
|
||||
private const RUNNER_LOCK_NAME = 'scheduled_jobs_runner';
|
||||
|
||||
public static function runDueJobs(int $limit = 20): array
|
||||
public function __construct(
|
||||
private readonly ScheduledJobService $scheduledJobService,
|
||||
private readonly ScheduledJobRepository $scheduledJobRepository,
|
||||
private readonly ScheduledJobRunRepository $scheduledJobRunRepository,
|
||||
private readonly SchedulerRuntimeRepository $schedulerRuntimeRepository,
|
||||
private readonly ScheduledJobRegistry $scheduledJobRegistry,
|
||||
private readonly ScheduleCalculator $scheduleCalculator
|
||||
) {
|
||||
}
|
||||
|
||||
public function runDueJobs(int $limit = 20): array
|
||||
{
|
||||
ScheduledJobService::ensureSystemJobs();
|
||||
$this->scheduledJobService->ensureSystemJobs();
|
||||
$startedAt = microtime(true);
|
||||
$result = [
|
||||
'ok' => true,
|
||||
@@ -34,19 +44,19 @@ class SchedulerRunService
|
||||
'skipped' => 0,
|
||||
];
|
||||
|
||||
if (!self::acquireRunnerLock()) {
|
||||
if (!$this->acquireRunnerLock()) {
|
||||
$result['ok'] = false;
|
||||
$result['error'] = 'lock_not_acquired';
|
||||
$result['duration_ms'] = self::durationMs($startedAt);
|
||||
self::updateRuntimeHeartbeat('lock_not_acquired', 'lock_not_acquired');
|
||||
$result['duration_ms'] = $this->durationMs($startedAt);
|
||||
$this->updateRuntimeHeartbeat('lock_not_acquired', 'lock_not_acquired');
|
||||
return $result;
|
||||
}
|
||||
|
||||
try {
|
||||
$nowUtc = gmdate('Y-m-d H:i:s');
|
||||
$dueJobs = ScheduledJobRepository::listDueJobs($nowUtc, $limit);
|
||||
$dueJobs = $this->scheduledJobRepository->listDueJobs($nowUtc, $limit);
|
||||
foreach ($dueJobs as $job) {
|
||||
$run = self::runOne($job, 'scheduler', null, false);
|
||||
$run = $this->runOne($job, 'scheduler', null, false);
|
||||
$result['processed']++;
|
||||
$status = (string) ($run['status'] ?? 'failed');
|
||||
if ($status === 'success') {
|
||||
@@ -61,46 +71,46 @@ class SchedulerRunService
|
||||
$result['ok'] = false;
|
||||
$result['error'] = 'unexpected_error';
|
||||
} finally {
|
||||
self::releaseRunnerLock();
|
||||
$result['duration_ms'] = self::durationMs($startedAt);
|
||||
$this->releaseRunnerLock();
|
||||
$result['duration_ms'] = $this->durationMs($startedAt);
|
||||
if ($result['ok']) {
|
||||
self::updateRuntimeHeartbeat('ok', null);
|
||||
$this->updateRuntimeHeartbeat('ok', null);
|
||||
} else {
|
||||
$errorCode = self::nullableString($result['error'] ?? null) ?? 'unexpected_error';
|
||||
self::updateRuntimeHeartbeat('unexpected_error', $errorCode);
|
||||
$errorCode = $this->nullableString($result['error']) ?? 'unexpected_error';
|
||||
$this->updateRuntimeHeartbeat('unexpected_error', $errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function runJobNow(int $jobId, int $actorUserId): array
|
||||
public function runJobNow(int $jobId, int $actorUserId): array
|
||||
{
|
||||
ScheduledJobService::ensureSystemJobs();
|
||||
$this->scheduledJobService->ensureSystemJobs();
|
||||
if ($jobId <= 0 || $actorUserId <= 0) {
|
||||
return ['ok' => false, 'error' => 'invalid_request'];
|
||||
}
|
||||
if (!self::acquireRunnerLock()) {
|
||||
if (!$this->acquireRunnerLock()) {
|
||||
return ['ok' => false, 'error' => 'lock_not_acquired'];
|
||||
}
|
||||
|
||||
try {
|
||||
$job = ScheduledJobRepository::find($jobId);
|
||||
$job = $this->scheduledJobRepository->find($jobId);
|
||||
if (!$job) {
|
||||
return ['ok' => false, 'error' => 'job_not_found'];
|
||||
}
|
||||
$run = self::runOne($job, 'manual', $actorUserId, true);
|
||||
$run = $this->runOne($job, 'manual', $actorUserId, true);
|
||||
return [
|
||||
'ok' => in_array((string) ($run['status'] ?? ''), ['success', 'skipped'], true),
|
||||
'status' => (string) ($run['status'] ?? 'failed'),
|
||||
'error' => (string) ($run['error_code'] ?? ''),
|
||||
];
|
||||
} finally {
|
||||
self::releaseRunnerLock();
|
||||
$this->releaseRunnerLock();
|
||||
}
|
||||
}
|
||||
|
||||
private static function runOne(array $job, string $triggerType, ?int $actorUserId, bool $force): array
|
||||
private function runOne(array $job, string $triggerType, ?int $actorUserId, bool $force): array
|
||||
{
|
||||
$jobId = (int) ($job['id'] ?? 0);
|
||||
$nowUtc = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
|
||||
@@ -115,9 +125,9 @@ class SchedulerRunService
|
||||
return ['status' => 'skipped', 'error_code' => 'job_disabled', 'error_message' => null];
|
||||
}
|
||||
|
||||
$markedRunning = ScheduledJobRepository::markRunning($jobId, $startedAtUtc);
|
||||
$markedRunning = $this->scheduledJobRepository->markRunning($jobId, $startedAtUtc);
|
||||
if (!$markedRunning) {
|
||||
self::insertRunLog([
|
||||
$this->insertRunLog([
|
||||
'job_id' => $jobId,
|
||||
'job_key' => (string) ($job['job_key'] ?? ''),
|
||||
'trigger_type' => $triggerType,
|
||||
@@ -140,17 +150,17 @@ class SchedulerRunService
|
||||
$resultPayload = [];
|
||||
|
||||
try {
|
||||
$definition = ScheduledJobRegistry::get((string) ($job['job_key'] ?? ''));
|
||||
$definition = $this->scheduledJobRegistry->get((string) ($job['job_key'] ?? ''));
|
||||
if (!$definition) {
|
||||
$status = 'failed';
|
||||
$errorCode = 'job_not_registered';
|
||||
} else {
|
||||
$execution = ScheduledJobRegistry::execute((string) $job['job_key'], $actorUserId);
|
||||
$execution = $this->scheduledJobRegistry->execute((string) $job['job_key'], $actorUserId);
|
||||
$status = in_array((string) $execution['status'], ['success', 'failed', 'skipped'], true)
|
||||
? (string) $execution['status']
|
||||
: 'failed';
|
||||
$errorCode = self::nullableString($execution['error_code']);
|
||||
$errorMessage = self::nullableString($execution['error_message']);
|
||||
$errorCode = $this->nullableString($execution['error_code']);
|
||||
$errorMessage = $this->nullableString($execution['error_message']);
|
||||
$resultPayload = $execution['result'];
|
||||
}
|
||||
} catch (\Throwable $exception) {
|
||||
@@ -168,14 +178,14 @@ class SchedulerRunService
|
||||
if ($triggerType === 'scheduler' && (int) ($job['catchup_once'] ?? 1) === 0 && !empty($job['next_run_at'])) {
|
||||
// catchup_once = 0: advance next_run_at strictly from the originally scheduled
|
||||
// time, skipping any windows that already passed. This prevents a backlog storm
|
||||
// after downtime – missed slots are dropped, only the next future slot is kept.
|
||||
// after downtime - missed slots are dropped, only the next future slot is kept.
|
||||
// The guard cap (500) prevents an infinite loop if calculateNextRunUtc has a bug
|
||||
// and keeps returning a time that is still in the past.
|
||||
$reference = self::parseUtc((string) $job['next_run_at']) ?? $finishedAt;
|
||||
$next = ScheduleCalculator::calculateNextRunUtc($job, $reference);
|
||||
$reference = $this->parseUtc((string) $job['next_run_at']) ?? $finishedAt;
|
||||
$next = $this->scheduleCalculator->calculateNextRunUtc($job, $reference);
|
||||
$guard = 0;
|
||||
while ($next && $next <= $finishedAt && $guard < 500) {
|
||||
$next = ScheduleCalculator::calculateNextRunUtc($job, $next);
|
||||
$next = $this->scheduleCalculator->calculateNextRunUtc($job, $next);
|
||||
$guard++;
|
||||
}
|
||||
$nextRunUtc = $next ? $next->format('Y-m-d H:i:s') : null;
|
||||
@@ -183,12 +193,12 @@ class SchedulerRunService
|
||||
// catchup_once = 1 (default): calculate next_run_at from the actual finish time.
|
||||
// If a run was delayed or a slot was missed, exactly one catch-up run is scheduled
|
||||
// starting from now, then normal scheduling resumes.
|
||||
$next = ScheduleCalculator::calculateNextRunUtc($job, $finishedAt);
|
||||
$next = $this->scheduleCalculator->calculateNextRunUtc($job, $finishedAt);
|
||||
$nextRunUtc = $next ? $next->format('Y-m-d H:i:s') : null;
|
||||
}
|
||||
}
|
||||
|
||||
ScheduledJobRepository::finishRun(
|
||||
$this->scheduledJobRepository->finishRun(
|
||||
$jobId,
|
||||
$status,
|
||||
$startedAtUtc,
|
||||
@@ -198,7 +208,7 @@ class SchedulerRunService
|
||||
$errorMessage
|
||||
);
|
||||
|
||||
self::insertRunLog([
|
||||
$this->insertRunLog([
|
||||
'job_id' => $jobId,
|
||||
'job_key' => (string) ($job['job_key'] ?? ''),
|
||||
'trigger_type' => $triggerType,
|
||||
@@ -209,16 +219,16 @@ class SchedulerRunService
|
||||
'duration_ms' => $durationMs,
|
||||
'error_code' => $errorCode,
|
||||
'error_message' => $errorMessage,
|
||||
'result_json' => self::encodeResult($resultPayload),
|
||||
'result_json' => $this->encodeResult($resultPayload),
|
||||
]);
|
||||
|
||||
return ['status' => $status, 'error_code' => $errorCode, 'error_message' => $errorMessage];
|
||||
}
|
||||
|
||||
private static function insertRunLog(array $data): void
|
||||
private function insertRunLog(array $data): void
|
||||
{
|
||||
try {
|
||||
ScheduledJobRunRepository::create([
|
||||
$this->scheduledJobRunRepository->create([
|
||||
'run_uuid' => RepoQuery::uuidV4(),
|
||||
'job_id' => (int) ($data['job_id'] ?? 0),
|
||||
'job_key' => (string) ($data['job_key'] ?? ''),
|
||||
@@ -237,7 +247,7 @@ class SchedulerRunService
|
||||
}
|
||||
}
|
||||
|
||||
private static function encodeResult(array $result): ?string
|
||||
private function encodeResult(array $result): ?string
|
||||
{
|
||||
if (!$result) {
|
||||
return null;
|
||||
@@ -246,7 +256,7 @@ class SchedulerRunService
|
||||
return is_string($encoded) ? $encoded : null;
|
||||
}
|
||||
|
||||
private static function parseUtc(string $value): ?\DateTimeImmutable
|
||||
private function parseUtc(string $value): ?\DateTimeImmutable
|
||||
{
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
@@ -259,28 +269,28 @@ class SchedulerRunService
|
||||
}
|
||||
}
|
||||
|
||||
private static function nullableString(mixed $value): ?string
|
||||
private function nullableString(mixed $value): ?string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
return $value !== '' ? $value : null;
|
||||
}
|
||||
|
||||
private static function acquireRunnerLock(): bool
|
||||
private function acquireRunnerLock(): bool
|
||||
{
|
||||
$got = DB::selectValue('select GET_LOCK(?, 0) as got_lock', self::RUNNER_LOCK_NAME);
|
||||
return (int) $got === 1;
|
||||
}
|
||||
|
||||
private static function updateRuntimeHeartbeat(string $status, ?string $errorCode): void
|
||||
private function updateRuntimeHeartbeat(string $status, ?string $errorCode): void
|
||||
{
|
||||
try {
|
||||
SchedulerRuntimeRepository::touchHeartbeat($status, $errorCode);
|
||||
$this->schedulerRuntimeRepository->touchHeartbeat($status, $errorCode);
|
||||
} catch (\Throwable $exception) {
|
||||
// fail-open
|
||||
}
|
||||
}
|
||||
|
||||
private static function releaseRunnerLock(): void
|
||||
private function releaseRunnerLock(): void
|
||||
{
|
||||
try {
|
||||
DB::selectValue('select RELEASE_LOCK(?) as released_lock', self::RUNNER_LOCK_NAME);
|
||||
@@ -289,7 +299,7 @@ class SchedulerRunService
|
||||
}
|
||||
}
|
||||
|
||||
private static function durationMs(float $startedAt): int
|
||||
private function durationMs(float $startedAt): int
|
||||
{
|
||||
return (int) max(0, round((microtime(true) - $startedAt) * 1000));
|
||||
}
|
||||
|
||||
83
lib/Service/Scheduler/SchedulerServicesFactory.php
Normal file
83
lib/Service/Scheduler/SchedulerServicesFactory.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Service\Scheduler;
|
||||
|
||||
use MintyPHP\Repository\Scheduler\ScheduledJobRepository;
|
||||
use MintyPHP\Repository\Scheduler\ScheduledJobRunRepository;
|
||||
use MintyPHP\Repository\Scheduler\SchedulerRuntimeRepository;
|
||||
use MintyPHP\Service\User\UserLifecycleService;
|
||||
use MintyPHP\Service\User\UserServicesFactory;
|
||||
|
||||
class SchedulerServicesFactory
|
||||
{
|
||||
private ?ScheduledJobRepository $scheduledJobRepository = null;
|
||||
private ?ScheduledJobRunRepository $scheduledJobRunRepository = null;
|
||||
private ?SchedulerRuntimeRepository $schedulerRuntimeRepository = null;
|
||||
private ?ScheduleCalculator $scheduleCalculator = null;
|
||||
private ?UserServicesFactory $userServicesFactory = null;
|
||||
private ?ScheduledJobRegistry $scheduledJobRegistry = null;
|
||||
private ?ScheduledJobService $scheduledJobService = null;
|
||||
private ?SchedulerRunService $schedulerRunService = null;
|
||||
|
||||
public function createScheduledJobService(): ScheduledJobService
|
||||
{
|
||||
return $this->scheduledJobService ??= new ScheduledJobService(
|
||||
$this->getScheduledJobRepository(),
|
||||
$this->getScheduledJobRunRepository(),
|
||||
$this->getScheduledJobRegistry(),
|
||||
$this->getScheduleCalculator()
|
||||
);
|
||||
}
|
||||
|
||||
public function createSchedulerRunService(): SchedulerRunService
|
||||
{
|
||||
return $this->schedulerRunService ??= new SchedulerRunService(
|
||||
$this->createScheduledJobService(),
|
||||
$this->getScheduledJobRepository(),
|
||||
$this->getScheduledJobRunRepository(),
|
||||
$this->getSchedulerRuntimeRepository(),
|
||||
$this->getScheduledJobRegistry(),
|
||||
$this->getScheduleCalculator()
|
||||
);
|
||||
}
|
||||
|
||||
public function createUserLifecycleService(): UserLifecycleService
|
||||
{
|
||||
return $this->getUserLifecycleService();
|
||||
}
|
||||
|
||||
private function getScheduledJobRepository(): ScheduledJobRepository
|
||||
{
|
||||
return $this->scheduledJobRepository ??= new ScheduledJobRepository();
|
||||
}
|
||||
|
||||
private function getScheduledJobRunRepository(): ScheduledJobRunRepository
|
||||
{
|
||||
return $this->scheduledJobRunRepository ??= new ScheduledJobRunRepository();
|
||||
}
|
||||
|
||||
private function getSchedulerRuntimeRepository(): SchedulerRuntimeRepository
|
||||
{
|
||||
return $this->schedulerRuntimeRepository ??= new SchedulerRuntimeRepository();
|
||||
}
|
||||
|
||||
private function getScheduleCalculator(): ScheduleCalculator
|
||||
{
|
||||
return $this->scheduleCalculator ??= new ScheduleCalculator();
|
||||
}
|
||||
|
||||
private function getUserLifecycleService(): UserLifecycleService
|
||||
{
|
||||
return $this->getUserServicesFactory()->createUserLifecycleService();
|
||||
}
|
||||
|
||||
private function getScheduledJobRegistry(): ScheduledJobRegistry
|
||||
{
|
||||
return $this->scheduledJobRegistry ??= new ScheduledJobRegistry($this->getUserLifecycleService());
|
||||
}
|
||||
|
||||
private function getUserServicesFactory(): UserServicesFactory
|
||||
{
|
||||
return $this->userServicesFactory ??= new UserServicesFactory();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user