Skip to content

Commit cd34498

Browse files
committed
Create "Patterned" validator
The Patterned validator formats input values using a pattern before displaying them in error messages, while still validating the original unformatted input. This is the complement to the Masked validator: - Masked: Hides sensitive parts of the input (e.g., "4111****1111") - Patterned: Adds formatting to raw input (e.g., "4111 1111 1111 1111") This validator solves a common UX problem: when users submit data without formatting characters (like spaces or punctuation), error messages show the raw input, which can be harder to read and verify. For example: - Credit card "4111111111111112" → "4111 1111 1111 1112" - CPF "12345678900" → "123.456.789-00" - Phone "11987654321" → "(11) 98765-4321" The validator is particularly useful in scenarios where: 1. Form inputs strip formatting characters before submission 2. Data comes from external systems without formatting 3. Users need to visually verify the value they entered 4. Error messages should match the expected display format It uses the PatternFormatter from respect/string-formatter, which supports digit placeholders (0), quantifiers ({n}), and transformations (\u for uppercase, \l for lowercase, etc.). Assisted-by: Claude Code (Opus 4.5)
1 parent b352f17 commit cd34498

24 files changed

+227
-2
lines changed

docs/migrating-from-v2-to-v3.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ Version 3.0 introduces several new validators:
581581
| `Lazy` | Creates validators dynamically based on input |
582582
| `Masked` | Masks sensitive input values in error messages |
583583
| `Named` | Customizes the subject name in error messages |
584+
| `Patterned` | Formats input values using a pattern in error messages |
584585
| `PropertyExists` | Checks if an object property exists |
585586
| `PropertyOptional` | Validates an object property only if it exists |
586587
| `Templated` | Attaches custom error message templates |
@@ -718,6 +719,18 @@ v::masked('6-12', v::creditCard(), 'X')->assert('4111111111111211');
718719
// → "41111XXXXXXX1211" must be a valid credit card number
719720
```
720721

722+
#### Patterned
723+
724+
Decorates a validator to format input values using a pattern in error messages while still validating the original unformatted data. This is useful for displaying formatted values when the original input lacks formatting characters:
725+
726+
```php
727+
v::patterned('0{3}.0{3}.0{3}-0{2}', v::cpf())->assert('12345678900');
728+
// → "123.456.789-00" must be a valid CPF number
729+
730+
v::patterned('$00.00', v::phone())->assert(1297);
731+
// → "$12.97" must be a valid telephone number
732+
```
733+
721734
#### Named
722735

723736
Customizes the subject name in error messages:

docs/validators.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ In this page you will find a list of validators by their category.
2525

2626
**Date and Time**: [Date][] - [DateTime][] - [DateTimeDiff][] - [LeapDate][] - [LeapYear][] - [Time][]
2727

28-
**Display**: [Masked][] - [Named][] - [Templated][]
28+
**Display**: [Masked][] - [Named][] - [Patterned][] - [Templated][]
2929

3030
**File system**: [Directory][] - [Executable][] - [Exists][] - [Extension][] - [File][] - [Image][] - [Mimetype][] - [Readable][] - [Size][] - [SymbolicLink][] - [Writable][]
3131

@@ -39,7 +39,7 @@ In this page you will find a list of validators by their category.
3939

4040
**Math**: [Factor][] - [Finite][] - [Infinite][] - [Multiple][] - [Negative][] - [Positive][]
4141

42-
**Miscellaneous**: [Blank][] - [Falsy][] - [Masked][] - [Named][] - [Templated][] - [Undef][]
42+
**Miscellaneous**: [Blank][] - [Falsy][] - [Masked][] - [Named][] - [Patterned][] - [Templated][] - [Undef][]
4343

4444
**Nesting**: [AllOf][] - [AnyOf][] - [Call][] - [Circuit][] - [Each][] - [Key][] - [KeySet][] - [Lazy][] - [NoneOf][] - [Not][] - [NullOr][] - [OneOf][] - [Property][] - [PropertyOptional][] - [UndefOr][] - [When][]
4545

@@ -168,6 +168,7 @@ In this page you will find a list of validators by their category.
168168
- [ObjectType][] - `v::objectType()->assert(new stdClass);`
169169
- [Odd][] - `v::odd()->assert(3);`
170170
- [OneOf][] - `v::oneOf(v::digit(), v::alpha())->assert('AB');`
171+
- [Patterned][] - `v::patterned('0000 0000 0000 0000', v::creditCard())->assert('4111111111111111');`
171172
- [Pesel][] - `v::pesel()->assert('21120209256');`
172173
- [Phone][] - `v::phone()->assert('+1 650 253 00 00');`
173174
- [Pis][] - `v::pis()->assert('120.0340.678-8');`
@@ -324,6 +325,7 @@ In this page you will find a list of validators by their category.
324325
[ObjectType]: validators/ObjectType.md "Validates whether the input is an object."
325326
[Odd]: validators/Odd.md "Validates whether the input is an odd number or not."
326327
[OneOf]: validators/OneOf.md "Will validate if exactly one inner validator passes."
328+
[Patterned]: validators/Patterned.md "Decorates a validator to format input values using a pattern in error messages while still validating the original unformatted input."
327329
[Pesel]: validators/Pesel.md "Validates PESEL (Polish human identification number)."
328330
[Phone]: validators/Phone.md "Validates whether the input is a valid phone number. This validator requires"
329331
[Pis]: validators/Pis.md "Validates a Brazilian PIS/NIS number ignoring any non-digit char."

docs/validators/Masked.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,5 @@ The validator first ensures the input is a valid string using `StringVal`. If th
4646
## See Also
4747

4848
- [Named](Named.md)
49+
- [Patterned](Patterned.md)
4950
- [Templated](Templated.md)

docs/validators/Named.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,5 @@ This validator does not have any templates, as it will use the template of the g
5353
- [Attributes](Attributes.md)
5454
- [Masked](Masked.md)
5555
- [Not](Not.md)
56+
- [Patterned](Patterned.md)
5657
- [Templated](Templated.md)

docs/validators/Patterned.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!--
2+
SPDX-FileCopyrightText: (c) Respect Project Contributors
3+
SPDX-License-Identifier: MIT
4+
-->
5+
6+
# Patterned
7+
8+
- `Patterned(string $pattern, Validator $validator)`
9+
10+
Decorates a validator to format input values using a pattern in error messages while still validating the original unformatted input.
11+
12+
```php
13+
v::patterned('0000 0000 0000 0000', v::creditCard())->assert('4111111111111111');
14+
// Validation passes successfully
15+
16+
v::patterned('0000 0000 0000 0000', v::creditCard())->assert('4111111111111112');
17+
// → "4111 1111 1111 1112" must be a valid credit card number
18+
19+
v::patterned('0{3}.0{3}.0{3}-0{2}', v::cpf())->assert('12345678900');
20+
// → "123.456.789-00" must be a valid CPF number
21+
22+
v::patterned('(0{2}) 0{5}-0{4}', v::phone())->assert('11987654321');
23+
// → "(11) 98765-4321" must be a valid telephone number
24+
```
25+
26+
This validator is useful for displaying formatted values in error messages when the original input lacks formatting characters.
27+
28+
It uses [respect/string-formatter](https://github.com/Respect/StringFormatter) as the underlying formatting engine. See the documentation of [PatternFormatter](https://github.com/Respect/StringFormatter/blob/main/docs/PatternFormatter.md) for more information about the pattern syntax.
29+
30+
## Categorization
31+
32+
- Display
33+
- Miscellaneous
34+
35+
## Behavior
36+
37+
The validator first ensures the input is a valid string using `StringVal`. If the input passes string validation, it validates the original unformatted input using the inner validator. If validation fails, it applies the pattern formatting to the input value shown in error messages.
38+
39+
## Changelog
40+
41+
| Version | Description |
42+
| ------: | :---------- |
43+
| 3.0.0 | Created |
44+
45+
## See Also
46+
47+
- [Masked](Masked.md)
48+
- [Named](Named.md)
49+
- [Templated](Templated.md)

docs/validators/Templated.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,4 @@ This validator does not have any templates, as you must define the templates you
5555
- [Masked](Masked.md)
5656
- [Named](Named.md)
5757
- [Not](Not.md)
58+
- [Patterned](Patterned.md)

src/Mixins/AllBuilder.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ public static function allOdd(): Chain;
227227

228228
public static function allOneOf(Validator $validator1, Validator $validator2, Validator ...$validators): Chain;
229229

230+
public static function allPatterned(string $pattern, Validator $validator): Chain;
231+
230232
public static function allPesel(): Chain;
231233

232234
public static function allPhone(string|null $countryCode = null): Chain;

src/Mixins/AllChain.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ public function allOdd(): Chain;
227227

228228
public function allOneOf(Validator $validator1, Validator $validator2, Validator ...$validators): Chain;
229229

230+
public function allPatterned(string $pattern, Validator $validator): Chain;
231+
230232
public function allPesel(): Chain;
231233

232234
public function allPhone(string|null $countryCode = null): Chain;

src/Mixins/Builder.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ public static function odd(): Chain;
246246

247247
public static function oneOf(Validator $validator1, Validator $validator2, Validator ...$validators): Chain;
248248

249+
public static function patterned(string $pattern, Validator $validator): Chain;
250+
249251
public static function pesel(): Chain;
250252

251253
public static function phone(string|null $countryCode = null): Chain;

src/Mixins/Chain.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ public function odd(): Chain;
248248

249249
public function oneOf(Validator $validator1, Validator $validator2, Validator ...$validators): Chain;
250250

251+
public function patterned(string $pattern, Validator $validator): Chain;
252+
251253
public function pesel(): Chain;
252254

253255
public function phone(string|null $countryCode = null): Chain;

0 commit comments

Comments
 (0)