, * definitions:array> * } */ public function validate(): array { $errors = []; $definitions = []; foreach ($this->policy->coveredTypeNames() as $operationType) { $definition = $this->policy->definition($operationType); if ($definition === null) { $errors[] = sprintf('Missing lifecycle policy definition for [%s].', $operationType); continue; } $definitions[$operationType] = $definition; $jobClass = $this->policy->jobClass($operationType); if ($jobClass === null || ! class_exists($jobClass)) { $errors[] = sprintf('Lifecycle policy [%s] points to a missing job class.', $operationType); continue; } $timeout = $this->jobTimeoutSeconds($operationType); if (! is_int($timeout) || $timeout <= 0) { $errors[] = sprintf('Lifecycle policy [%s] requires an explicit positive job timeout.', $operationType); } if (! $this->jobFailsOnTimeout($operationType)) { $errors[] = sprintf('Lifecycle policy [%s] requires failOnTimeout=true.', $operationType); } if ($this->policy->requiresDirectFailedBridge($operationType) && ! $this->jobUsesDirectFailedBridge($operationType)) { $errors[] = sprintf('Lifecycle policy [%s] requires a direct failed-job bridge.', $operationType); } $retryAfter = $this->policy->queueRetryAfterSeconds($this->policy->queueConnection($operationType)); $safetyMargin = $this->policy->retryAfterSafetyMarginSeconds(); if (is_int($timeout) && is_int($retryAfter) && $timeout >= ($retryAfter - $safetyMargin)) { $errors[] = sprintf( 'Lifecycle policy [%s] has timeout %d which is not safely below retry_after %d (margin %d).', $operationType, $timeout, $retryAfter, $safetyMargin, ); } $expectedMaxRuntime = $this->policy->expectedMaxRuntimeSeconds($operationType); if (is_int($expectedMaxRuntime) && is_int($retryAfter) && $expectedMaxRuntime >= ($retryAfter - $safetyMargin)) { $errors[] = sprintf( 'Lifecycle policy [%s] expected runtime %d is not safely below retry_after %d (margin %d).', $operationType, $expectedMaxRuntime, $retryAfter, $safetyMargin, ); } } return [ 'valid' => $errors === [], 'errors' => $errors, 'definitions' => $definitions, ]; } public function assertValid(): void { $result = $this->validate(); if (($result['valid'] ?? false) === true) { return; } throw new RuntimeException(implode(' ', $result['errors'] ?? ['Lifecycle policy validation failed.'])); } public function jobTimeoutSeconds(string $operationType): ?int { $jobClass = $this->policy->jobClass($operationType); if ($jobClass === null || ! class_exists($jobClass)) { return null; } $timeout = get_class_vars($jobClass)['timeout'] ?? null; return is_numeric($timeout) ? (int) $timeout : null; } public function jobFailsOnTimeout(string $operationType): bool { $jobClass = $this->policy->jobClass($operationType); if ($jobClass === null || ! class_exists($jobClass)) { return false; } return (bool) (get_class_vars($jobClass)['failOnTimeout'] ?? false); } public function jobUsesDirectFailedBridge(string $operationType): bool { $jobClass = $this->policy->jobClass($operationType); if ($jobClass === null || ! class_exists($jobClass)) { return false; } return in_array(BridgesFailedOperationRun::class, class_uses_recursive($jobClass), true); } }