PluginProbe
404 Solution / 4.3.0
404 Solution v4.3.0
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / redirects / RedirectSpec.php

RedirectSpec.php in 404 Solution 4.3.0, at includes/redirects/RedirectSpec.php

226 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Immutable value object describing one redirect to be created via
9 * {@see ABJ_404_Solution_RedirectsRepositoryInterface::setupRedirect()}.
10 *
11 * Replaces an 8-position parameter list (queue task c738; audit source
12 * design-audit-2026-05-29.md, criterion 220 Interface Size). The previous
13 * positional signature mixed two URL strings ($fromURL and $final_dest) and
14 * five numeric/string fields with overlapping semantics, making call sites
15 * trivially transposable at the wrong end of the list.
16 *
17 * Construct via {@see self::fromArray()} at new call sites and read via the
18 * typed getters. Instances are immutable; there are no setters.
19 *
20 * Field semantics match the legacy positional parameters exactly:
21 * - fromURL: source URL the redirect matches (string).
22 * - status: ABJ404_STATUS_* discriminator (int|string, numeric expected).
23 * - type: ABJ404_TYPE_* discriminator (int|string, numeric expected).
24 * - finalDest: destination URL or numeric destination id (string).
25 * - code: HTTP status code (int|string, numeric expected).
26 * - disabled: 0/1 flag (int, default 0).
27 * - engine: optional engine name producing this redirect (string|null).
28 * - score: optional match score (float|null).
29 */
30 final class ABJ_404_Solution_RedirectSpec {
31
32 /** @var string */
33 private $fromURL;
34 /** @var int|string */
35 private $status;
36 /** @var int|string */
37 private $type;
38 /** @var string */
39 private $finalDest;
40 /** @var int|string */
41 private $code;
42 /** @var int */
43 private $disabled;
44 /** @var string|null */
45 private $engine;
46 /** @var float|null */
47 private $score;
48
49 /**
50 * @param string $fromURL
51 * @param int|string $status
52 * @param int|string $type
53 * @param string $finalDest
54 * @param int|string $code
55 * @param int $disabled
56 * @param string|null $engine
57 * @param float|null $score
58 */
59 private function __construct(
60 $fromURL,
61 $status,
62 $type,
63 $finalDest,
64 $code,
65 $disabled,
66 $engine,
67 $score
68 ) {
69 $this->fromURL = (string)$fromURL;
70 $this->status = $status;
71 $this->type = $type;
72 $this->finalDest = (string)$finalDest;
73 $this->code = $code;
74 $this->disabled = (int)$disabled;
75 $this->engine = ($engine === null) ? null : (string)$engine;
76 $this->score = ($score === null) ? null : (float)$score;
77 }
78
79 /**
80 * Legacy positional factory. Prefer {@see self::fromArray()} at new call
81 * sites so same-type fields are spelled out before construction.
82 *
83 * @param string $fromURL
84 * @param int|string $status
85 * @param int|string $type
86 * @param string $finalDest
87 * @param int|string $code
88 * @param int $disabled
89 * @param string|null $engine
90 * @param float|null $score
91 * @return self
92 */
93 public static function create(
94 $fromURL,
95 $status,
96 $type,
97 $finalDest,
98 $code,
99 $disabled = 0,
100 $engine = null,
101 $score = null
102 ): self {
103 return new self($fromURL, $status, $type, $finalDest, $code, $disabled, $engine, $score);
104 }
105
106 /**
107 * Build a redirect-create request from named fields.
108 *
109 * @param array<string, mixed> $fields
110 * @return self
111 */
112 public static function fromArray(array $fields): self {
113 return new self(
114 (string)self::requiredStringOrInt($fields, 'fromURL'),
115 self::requiredStringOrInt($fields, 'status'),
116 self::requiredStringOrInt($fields, 'type'),
117 (string)self::requiredStringOrInt($fields, 'finalDest'),
118 self::requiredStringOrInt($fields, 'code'),
119 array_key_exists('disabled', $fields) ? self::requiredInt($fields, 'disabled') : 0,
120 array_key_exists('engine', $fields) && $fields['engine'] !== null
121 ? self::requiredString($fields, 'engine')
122 : null,
123 array_key_exists('score', $fields) && $fields['score'] !== null
124 ? self::requiredFloat($fields, 'score')
125 : null
126 );
127 }
128
129 /**
130 * @param array<string, mixed> $fields
131 * @return int|string
132 */
133 private static function requiredStringOrInt(array $fields, string $name) {
134 if (!array_key_exists($name, $fields)) {
135 throw new InvalidArgumentException('RedirectSpec is missing required field: ' . $name);
136 }
137 if (!is_int($fields[$name]) && !is_string($fields[$name])) {
138 throw new InvalidArgumentException('RedirectSpec field must be string or int: ' . $name);
139 }
140 return $fields[$name];
141 }
142
143 /**
144 * @param array<string, mixed> $fields
145 */
146 private static function requiredString(array $fields, string $name): string {
147 if (!array_key_exists($name, $fields)) {
148 throw new InvalidArgumentException('RedirectSpec is missing required field: ' . $name);
149 }
150 if (!is_string($fields[$name])) {
151 throw new InvalidArgumentException('RedirectSpec field must be string: ' . $name);
152 }
153 return $fields[$name];
154 }
155
156 /**
157 * @param array<string, mixed> $fields
158 */
159 private static function requiredInt(array $fields, string $name): int {
160 if (!array_key_exists($name, $fields)) {
161 throw new InvalidArgumentException('RedirectSpec is missing required field: ' . $name);
162 }
163 if (!is_int($fields[$name]) && !(is_string($fields[$name]) && is_numeric($fields[$name]))) {
164 throw new InvalidArgumentException('RedirectSpec field must be int: ' . $name);
165 }
166 return (int)$fields[$name];
167 }
168
169 /**
170 * @param array<string, mixed> $fields
171 */
172 private static function requiredFloat(array $fields, string $name): float {
173 if (!array_key_exists($name, $fields)) {
174 throw new InvalidArgumentException('RedirectSpec is missing required field: ' . $name);
175 }
176 if (!is_int($fields[$name]) && !is_float($fields[$name]) && !(is_string($fields[$name]) && is_numeric($fields[$name]))) {
177 throw new InvalidArgumentException('RedirectSpec field must be numeric: ' . $name);
178 }
179 return (float)$fields[$name];
180 }
181
182 /** @return string */
183 public function getFromURL(): string { return $this->fromURL; }
184
185 /** @return int|string */
186 public function getStatus() { return $this->status; }
187
188 /** @return int|string */
189 public function getType() { return $this->type; }
190
191 /** @return string */
192 public function getFinalDest(): string { return $this->finalDest; }
193
194 /** @return int|string */
195 public function getCode() { return $this->code; }
196
197 /** @return int */
198 public function getDisabled(): int { return $this->disabled; }
199
200 /** @return string|null */
201 public function getEngine() { return $this->engine; }
202
203 /** @return float|null */
204 public function getScore() { return $this->score; }
205
206 /**
207 * Return a new spec with the fromURL replaced. Used by callers that need
208 * to normalize the URL after construction (e.g. relative-path conversion).
209 *
210 * @param string $fromURL
211 * @return self
212 */
213 public function withFromURL(string $fromURL): self {
214 return new self(
215 $fromURL,
216 $this->status,
217 $this->type,
218 $this->finalDest,
219 $this->code,
220 $this->disabled,
221 $this->engine,
222 $this->score
223 );
224 }
225 }
226