$nextSteps */ public function __construct( public string $internalCode, public string $operatorLabel, public string $shortExplanation, public string $actionability, public array $nextSteps = [], public bool $showNoActionNeeded = false, public ?string $diagnosticCodeLabel = null, ) { if (trim($this->internalCode) === '') { throw new InvalidArgumentException('Reason envelopes must preserve an internal code.'); } if (trim($this->operatorLabel) === '') { throw new InvalidArgumentException('Reason envelopes require an operator label.'); } if (trim($this->shortExplanation) === '') { throw new InvalidArgumentException('Reason envelopes require a short explanation.'); } if (! in_array($this->actionability, [ 'retryable_transient', 'permanent_configuration', 'prerequisite_missing', 'non_actionable', ], true)) { throw new InvalidArgumentException('Unsupported reason actionability: '.$this->actionability); } foreach ($this->nextSteps as $nextStep) { if (! $nextStep instanceof NextStepOption) { throw new InvalidArgumentException('Reason envelopes only support NextStepOption instances.'); } } } /** * @param array $data */ public static function fromArray(array $data): ?self { $internalCode = is_string($data['internal_code'] ?? null) ? trim((string) $data['internal_code']) : (is_string($data['internalCode'] ?? null) ? trim((string) $data['internalCode']) : ''); $operatorLabel = is_string($data['operator_label'] ?? null) ? trim((string) $data['operator_label']) : (is_string($data['operatorLabel'] ?? null) ? trim((string) $data['operatorLabel']) : ''); $shortExplanation = is_string($data['short_explanation'] ?? null) ? trim((string) $data['short_explanation']) : (is_string($data['shortExplanation'] ?? null) ? trim((string) $data['shortExplanation']) : ''); $actionability = is_string($data['actionability'] ?? null) ? trim((string) $data['actionability']) : ''; $nextSteps = is_array($data['next_steps'] ?? null) ? NextStepOption::collect($data['next_steps']) : (is_array($data['nextSteps'] ?? null) ? NextStepOption::collect($data['nextSteps']) : []); $showNoActionNeeded = (bool) ($data['show_no_action_needed'] ?? $data['showNoActionNeeded'] ?? false); $diagnosticCodeLabel = is_string($data['diagnostic_code_label'] ?? null) ? trim((string) $data['diagnostic_code_label']) : (is_string($data['diagnosticCodeLabel'] ?? null) ? trim((string) $data['diagnosticCodeLabel']) : null); if ($internalCode === '' || $operatorLabel === '' || $shortExplanation === '' || $actionability === '') { return null; } return new self( internalCode: $internalCode, operatorLabel: $operatorLabel, shortExplanation: $shortExplanation, actionability: $actionability, nextSteps: $nextSteps, showNoActionNeeded: $showNoActionNeeded, diagnosticCodeLabel: $diagnosticCodeLabel !== '' ? $diagnosticCodeLabel : null, ); } /** * @param array $nextSteps */ public function withNextSteps(array $nextSteps): self { return new self( internalCode: $this->internalCode, operatorLabel: $this->operatorLabel, shortExplanation: $this->shortExplanation, actionability: $this->actionability, nextSteps: $nextSteps, showNoActionNeeded: $this->showNoActionNeeded, diagnosticCodeLabel: $this->diagnosticCodeLabel, ); } public function firstNextStep(): ?NextStepOption { return $this->nextSteps[0] ?? null; } public function guidanceText(): ?string { $nextStep = $this->firstNextStep(); if ($nextStep instanceof NextStepOption) { return 'Next step: '.rtrim($nextStep->label, ". \t\n\r\0\x0B").'.'; } if ($this->showNoActionNeeded) { return 'No action needed.'; } return null; } /** * @return array */ public function toBodyLines(bool $includeGuidance = true): array { $lines = [ $this->operatorLabel, $this->shortExplanation, ]; if ($includeGuidance) { $guidance = $this->guidanceText(); if (is_string($guidance) && $guidance !== '') { $lines[] = $guidance; } } return array_values(array_filter($lines, static fn (?string $line): bool => is_string($line) && trim($line) !== '')); } public function diagnosticCode(): string { return $this->diagnosticCodeLabel !== null && trim($this->diagnosticCodeLabel) !== '' ? $this->diagnosticCodeLabel : $this->internalCode; } /** * @return array */ public function toLegacyNextSteps(): array { return array_values(array_map( static fn (NextStepOption $nextStep): array => $nextStep->toLegacyArray(), array_filter( $this->nextSteps, static fn (NextStepOption $nextStep): bool => $nextStep->kind === 'link' && $nextStep->destination !== null, ), )); } /** * @return array{ * internal_code: string, * operator_label: string, * short_explanation: string, * actionability: string, * next_steps: array, * show_no_action_needed: bool, * diagnostic_code_label: string * } */ public function toArray(): array { return [ 'internal_code' => $this->internalCode, 'operator_label' => $this->operatorLabel, 'short_explanation' => $this->shortExplanation, 'actionability' => $this->actionability, 'next_steps' => array_map( static fn (NextStepOption $nextStep): array => $nextStep->toArray(), $this->nextSteps, ), 'show_no_action_needed' => $this->showNoActionNeeded, 'diagnostic_code_label' => $this->diagnosticCode(), ]; } }