queryMode = $queryMode; $this->fromURL = (string)$fromURL; $this->status = $status; $this->type = $type; $this->finalDest = (string)$finalDest; $this->code = $code; $this->disabled = (int)$disabled; $this->engine = ($engine === null) ? null : (string)$engine; $this->score = ($score === null) ? null : (float)$score; $this->timestamp = $timestamp; } /** * Build a redirect-create request from named fields (the only factory). * * Required keys: fromURL, status, type, finalDest, code (int|string each). * Optional keys: disabled (int or numeric string, default 0), engine * (string|null), score (numeric|null). A missing required key or a value * of any other type throws InvalidArgumentException, so callers holding a * loosely typed value cast it before the call. * * @param array $fields * @return self */ public static function fromArray(array $fields): self { return new self( (string)self::requiredStringOrInt($fields, 'fromURL'), self::requiredStringOrInt($fields, 'status'), self::requiredStringOrInt($fields, 'type'), (string)self::requiredStringOrInt($fields, 'finalDest'), self::requiredStringOrInt($fields, 'code'), array_key_exists('disabled', $fields) ? self::requiredInt($fields, 'disabled') : 0, array_key_exists('engine', $fields) && $fields['engine'] !== null ? self::requiredString($fields, 'engine') : null, array_key_exists('score', $fields) && $fields['score'] !== null ? self::requiredFloat($fields, 'score') : null, array_key_exists('timestamp', $fields) && $fields['timestamp'] !== null ? self::requiredInt($fields, 'timestamp') : null, ABJ_404_Solution_QueryStringHelper::queryMode($fields['query_mode'] ?? null) ); } /** * Return the named, stable representation used by durable intent JSON. * * The explicit key order is part of the canonical payload hash contract; * callers must not serialize the object or infer its fields positionally. * * @return array */ public function toArray(): array { return array( 'fromURL' => $this->fromURL, 'status' => $this->status, 'type' => $this->type, 'finalDest' => $this->finalDest, 'code' => $this->code, 'disabled' => $this->disabled, 'engine' => $this->engine, 'score' => $this->score, 'timestamp' => $this->timestamp, 'query_mode' => $this->queryMode, ); } /** * @param array $fields * @return int|string */ private static function requiredStringOrInt(array $fields, string $name) { if (!array_key_exists($name, $fields)) { throw new InvalidArgumentException('RedirectSpec is missing required field: ' . $name); } if (!is_int($fields[$name]) && !is_string($fields[$name])) { throw new InvalidArgumentException('RedirectSpec field must be string or int: ' . $name); } return $fields[$name]; } /** * @param array $fields */ private static function requiredString(array $fields, string $name): string { if (!array_key_exists($name, $fields)) { throw new InvalidArgumentException('RedirectSpec is missing required field: ' . $name); } if (!is_string($fields[$name])) { throw new InvalidArgumentException('RedirectSpec field must be string: ' . $name); } return $fields[$name]; } /** * @param array $fields */ private static function requiredInt(array $fields, string $name): int { if (!array_key_exists($name, $fields)) { throw new InvalidArgumentException('RedirectSpec is missing required field: ' . $name); } if (!is_int($fields[$name]) && !(is_string($fields[$name]) && is_numeric($fields[$name]))) { throw new InvalidArgumentException('RedirectSpec field must be int: ' . $name); } return (int)$fields[$name]; } /** * @param array $fields */ private static function requiredFloat(array $fields, string $name): float { if (!array_key_exists($name, $fields)) { throw new InvalidArgumentException('RedirectSpec is missing required field: ' . $name); } if (!is_int($fields[$name]) && !is_float($fields[$name]) && !(is_string($fields[$name]) && is_numeric($fields[$name]))) { throw new InvalidArgumentException('RedirectSpec field must be numeric: ' . $name); } return (float)$fields[$name]; } /** @return string|null */ public function getQueryMode(): ?string { return $this->queryMode; } /** @return string */ public function getFromURL(): string { return $this->fromURL; } /** @return int|string */ public function getStatus() { return $this->status; } /** @return int|string */ public function getType() { return $this->type; } /** @return string */ public function getFinalDest(): string { return $this->finalDest; } /** @return int|string */ public function getCode() { return $this->code; } /** @return int */ public function getDisabled(): int { return $this->disabled; } /** @return string|null */ public function getEngine() { return $this->engine; } /** @return float|null */ public function getScore() { return $this->score; } /** @return int|null */ public function getTimestamp(): ?int { return $this->timestamp; } /** * Return a new spec with the fromURL replaced. Used by callers that need * to normalize the URL after construction (e.g. relative-path conversion). * * @param string $fromURL * @return self */ public function withFromURL(string $fromURL): self { return new self( $fromURL, $this->status, $this->type, $this->finalDest, $this->code, $this->disabled, $this->engine, $this->score, $this->timestamp, $this->queryMode ); } }