= 0 * - url : string * - type : int (one of ABJ404_TYPE_*; 0 if absent / unparseable) * - status : int (one of ABJ404_STATUS_*; 0 if absent / unparseable) * - finalDest : string ('0' if absent) * - code : string (HTTP code; '' if absent) * - engine : string (engine name or '' if absent) * - startTs : int >= 0 (0 = no schedule) * - endTs : int >= 0 (0 = no schedule) * * Construct via `fromRaw()` on the consumer side; the DAO continues to * return raw arrays for backwards compatibility (typed accessors layer on * top, no producer rewrite required). */ final class ABJ_404_Solution_RedirectRow { /** @var int */ private $id; /** @var string */ private $url; /** @var int */ private $type; /** @var int */ private $status; /** @var string */ private $finalDest; /** @var string */ private $code; /** @var string */ private $engine; /** @var int */ private $startTs; /** @var int */ private $endTs; private function __construct( int $id, string $url, int $type, int $status, string $finalDest, string $code, string $engine, int $startTs, int $endTs ) { $this->id = $id; $this->url = $url; $this->type = $type; $this->status = $status; $this->finalDest = $finalDest; $this->code = $code; $this->engine = $engine; $this->startTs = $startTs; $this->endTs = $endTs; } /** * Normalize a raw DB row into a typed VO, or null when the payload is * unrecoverably malformed (not an array, or missing both id and url so * the row cannot be acted on). * * Callers MUST NOT shape-probe the raw payload themselves. * * @param mixed $raw */ public static function fromRaw($raw): ?self { if (!is_array($raw)) { return null; } $hasId = array_key_exists('id', $raw); $hasUrl = array_key_exists('url', $raw); if (!$hasId && !$hasUrl) { return null; } $id = self::coerceNonNegativeInt($raw, 'id'); $url = self::coerceString($raw, 'url'); $type = self::coerceInt($raw, 'type'); $status = self::coerceInt($raw, 'status'); $finalDest = self::coerceFinalDest($raw); $code = self::coerceString($raw, 'code'); $engine = trim(self::coerceString($raw, 'engine')); $startTs = self::coerceNonNegativeInt($raw, 'start_ts'); $endTs = self::coerceNonNegativeInt($raw, 'end_ts'); return new self($id, $url, $type, $status, $finalDest, $code, $engine, $startTs, $endTs); } /** * @param array> $rows Raw rows from the DAO. * @return array Normalized rows; malformed rows are skipped. */ public static function fromRawList(array $rows): array { $result = array(); foreach ($rows as $row) { $vo = self::fromRaw($row); if ($vo !== null) { $result[] = $vo; } } return $result; } public function getId(): int { return $this->id; } public function getUrl(): string { return $this->url; } public function getType(): int { return $this->type; } public function getStatus(): int { return $this->status; } public function isRegex(): bool { return defined('ABJ404_STATUS_REGEX') && $this->status === (int)ABJ404_STATUS_REGEX; } public function isExternal(): bool { return defined('ABJ404_TYPE_EXTERNAL') && $this->type === (int)ABJ404_TYPE_EXTERNAL; } public function isFourOhFourDisplayed(): bool { return defined('ABJ404_TYPE_404_DISPLAYED') && $this->type === (int)ABJ404_TYPE_404_DISPLAYED; } /** Stored verbatim. May be a numeric post ID or a URL (external redirect). */ public function getFinalDest(): string { return $this->finalDest; } public function hasFinalDest(): bool { $t = trim($this->finalDest); return $t !== '' && $t !== '0'; } public function getCode(): string { return $this->code; } public function getEngine(): string { return $this->engine; } public function getStartTs(): int { return $this->startTs; } public function getEndTs(): int { return $this->endTs; } /** * @param array $raw */ private static function coerceString(array $raw, string $key): string { if (!isset($raw[$key])) { return ''; } $v = $raw[$key]; if (is_string($v)) { return $v; } if (is_scalar($v)) { return (string)$v; } return ''; } /** * @param array $raw */ private static function coerceInt(array $raw, string $key): int { if (!isset($raw[$key])) { return 0; } $v = $raw[$key]; if (is_int($v)) { return $v; } if (is_float($v)) { return (int)$v; } if (is_string($v) && is_numeric($v)) { return (int)$v; } if (is_bool($v)) { return $v ? 1 : 0; } return 0; } /** * @param array $raw */ private static function coerceNonNegativeInt(array $raw, string $key): int { $i = self::coerceInt($raw, $key); return $i < 0 ? 0 : $i; } /** * `final_dest` is stored as a string column but historically populated * with both integer post-IDs and external URLs. Old rows can have an * absent/null/0 value. * * @param array $raw */ private static function coerceFinalDest(array $raw): string { if (!isset($raw['final_dest'])) { return '0'; } $v = $raw['final_dest']; if (is_string($v)) { return $v; } if (is_scalar($v)) { return (string)$v; } return '0'; } }