-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCheckResult.php
More file actions
234 lines (214 loc) · 6.81 KB
/
Copy pathCheckResult.php
File metadata and controls
234 lines (214 loc) · 6.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
/**
* Italix Rules - CheckResult
*
* @package Italix\Rules
* @license MPL-2.0
*/
declare(strict_types=1);
namespace Italix\Rules;
/**
* The verdict on a single value.
*
* Carries:
*
* - whether the value passed;
* - **whether it was actually verified**, which is not the same thing;
* - a machine-readable reason when it did not pass ('checksum', 'length', …),
* so the application can translate it — the engine never returns prose;
* - the canonical form of the value ('it60 x054 …' -> 'IT60X054…'), so the
* caller can store what it just validated rather than what was typed.
*
* ## Why there are three outcomes and not two
*
* A check that has no algorithm for a value's country used to answer `pass`.
* That is a lie with a straight face: a made-up German VAT number came back
* valid because the check only knew the Italian one and let everything else
* through a shape test. Measured, before this was fixed.
*
* So there is a third answer: **unknown** — "nothing here contradicts it, and
* nothing here confirms it".
*
* `is_valid()` stays **true** for unknown, deliberately. Making it false would
* turn every foreign identifier into a blocking error overnight, in every
* application already using this library. Instead the new question gets a new
* predicate — `is_verified()` — so existing code keeps its behaviour and code
* that cares can ask.
*
* Example:
*
* $result = $checker->check('iban', $value);
* if (!$result->is_valid()) {
* $errors['fund_iban'] = $t->get('rules.iban.' . $result->error_code());
* } else {
* $data['fund_iban'] = $result->normalized();
*
* if (!$result->is_verified()) {
* $notes[] = 'not checkable here — verify by hand';
* }
* }
*/
class CheckResult
{
/** @var bool */
private $valid;
/** @var bool Whether an actual algorithm ran, as opposed to nothing objecting */
private $verified;
/** @var string Canonical form of the value; equals the input when unchanged */
private $normalized;
/** @var string|null Machine-readable failure reason, null when valid */
private $error_c;
/** @var array Extra context for the message, e.g. ['expected' => 27] */
private $params;
/**
* @param bool $valid
* @param string $normalized
* @param string|null $error_c
* @param array $params
*/
private function __construct(
bool $valid,
string $normalized,
?string $error_c = null,
array $params = [],
bool $verified = true
) {
$this->valid = $valid;
$this->normalized = $normalized;
$this->error_c = $error_c;
$this->params = $params;
$this->verified = $verified;
}
/**
* @param string $normalized The canonical form of the accepted value
* @return self
*/
public static function pass(string $normalized): self
{
return new self(true, $normalized);
}
/**
* @param string $error_c Machine-readable reason, e.g. 'checksum'
* @param string $normalized Best-effort canonical form (for echoing back)
* @param array $params Extra context for the message
* @return self
*/
public static function fail(string $error_c, string $normalized = '', array $params = []): self
{
return new self(false, $normalized, $error_c, $params);
}
/**
* Nothing contradicted the value, and nothing confirmed it either.
*
* For the case a check genuinely cannot answer — most often a country it
* carries no algorithm for. The alternative, answering `pass`, is a
* confident wrong answer; answering `fail` would reject data that is very
* probably fine.
*
* `$params` should say why, e.g. `['country_c' => 'DE']`, so a report can
* name the reason without the library inventing a sentence.
*
* @param string $normalized Best-effort canonical form
* @param array $params Context — conventionally the country
* @return self
*/
public static function unknown(string $normalized, array $params = []): self
{
return new self(true, $normalized, null, $params, false);
}
/**
* True when nothing objected — including when nothing was actually checked.
*
* Pair with {@see is_verified()} before treating a value as confirmed.
*
* @return bool
*/
public function is_valid(): bool
{
return $this->valid;
}
/**
* True when an algorithm actually ran and reached a conclusion.
*
* False only for {@see unknown()} — a passing check and a failing one have
* both concluded something.
*
* @return bool
*/
public function is_verified(): bool
{
return $this->verified;
}
/**
* True when the value passed *and* the passing was earned.
*
* The predicate most callers want when the answer decides something
* irreversible.
*
* @return bool
*/
public function is_confirmed(): bool
{
return $this->valid && $this->verified;
}
/**
* Machine-readable failure reason; null when the value passed.
*
* @return string|null
*/
public function error_code(): ?string
{
return $this->error_c;
}
/**
* Canonical form of the value — safe to persist when is_valid() is true.
*
* @return string
*/
public function normalized(): string
{
return $this->normalized;
}
/**
* @return array
*/
/**
* The same verdict, carrying more context.
*
* A dispatcher answers with the verdict of the check it chose, but the
* caller also needs to know *what was chosen* — that a national id was
* verified as a register number and not a tax one. Immutable, like the rest
* of this object: a result that could be edited is a result nobody can rely
* on having read.
*
* @param array<string, mixed> $params merged over the existing ones
*/
public function with_params(array $params): self
{
$copy = clone $this;
$copy->params = $params + $this->params;
return $copy;
}
public function get_params(): array
{
return $this->params;
}
/**
* @return array
*/
public function to_array(): array
{
return [
'valid' => $this->valid,
'verified' => $this->verified,
'error_c' => $this->error_c,
'normalized' => $this->normalized,
'params' => $this->params,
];
}
}