Skip to content

Commit 8b8a506

Browse files
committed
docs(readme): complete revision — fix broken links, update stats and badges
Fixes: - ADR links: replaced 3 non-existent ADR files with the real 5 ADRs - SPEC links: replaced 2 non-existent SPECs with the real 3 SPECs - ARFA badge: 1.3 → 1.43 V4.0 - Project stats: 50→51 src files, 15→20 test files, 969→1938 test lines - Coverage: add 100% / 48 classes metric - Tests/Assertions: add 175 / 425 baseline Additions: - CI + Tests + Coverage badges (GitHub Actions) - 'Rule Parameters' section with truncate/pad/round/clamp/normalize_date examples - 'Custom Rules' section with PhoneRule implementation example (ARFA passthrough) - All 8 Architecture namespace entries (Event, Integration, Result, etc.) - SanitizerEngine import in Attribute-Driven example
1 parent 4a63611 commit 8b8a506

1 file changed

Lines changed: 107 additions & 50 deletions

File tree

README.md

Lines changed: 107 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22

33
<div align="center">
44

5+
[![CI](https://github.com/KaririCode-Framework/kariricode-sanitizer/actions/workflows/ci.yml/badge.svg)](https://github.com/KaririCode-Framework/kariricode-sanitizer/actions/workflows/ci.yml)
56
[![PHP 8.4+](https://img.shields.io/badge/PHP-8.4%2B-777BB4?logo=php&logoColor=white)](https://www.php.net/)
67
[![License: MIT](https://img.shields.io/badge/License-MIT-22c55e.svg)](LICENSE)
78
[![PHPStan Level 9](https://img.shields.io/badge/PHPStan-Level%209-4F46E5)](https://phpstan.org/)
8-
[![Rules](https://img.shields.io/badge/Rules-33-22c55e)](https://kariricode.org)
9-
[![Zero Dependencies](https://img.shields.io/badge/Dependencies-0-22c55e)](composer.json)
10-
[![ARFA](https://img.shields.io/badge/ARFA-1.3-orange)](https://kariricode.org)
9+
[![Tests](https://img.shields.io/badge/Tests-175%20passing-22c55e)](https://github.com/KaririCode-Framework/kariricode-sanitizer/actions)
10+
[![Coverage](https://img.shields.io/badge/Coverage-100%25-22c55e)](https://github.com/KaririCode-Framework/kariricode-sanitizer/actions)
11+
[![Rules](https://img.shields.io/badge/Rules-33-22c55e)](docs/spec/SPEC-002-rule-reference.md)
12+
[![ARFA](https://img.shields.io/badge/ARFA-1.43-orange)](https://kariricode.org)
1113
[![KaririCode Framework](https://img.shields.io/badge/KaririCode-Framework-orange)](https://kariricode.org)
1214

1315
**Composable, rule-based data sanitization engine for PHP 8.4+ — 33 rules, zero dependencies.**
1416

15-
[Installation](#installation) · [Quick Start](#quick-start) · [XSS Prevention](#xss-prevention) · [All Rules](#all-33-rules) · [Architecture](#architecture)
17+
[Installation](#installation) · [Quick Start](#quick-start) · [Attribute DTO](#attribute-driven-dto-sanitization) · [All Rules](#all-33-rules) · [Architecture](#architecture) · [Docs](docs/README.md)
1618

1719
</div>
1820

@@ -27,7 +29,7 @@ Raw user input arrives dirty — whitespace, wrong case, dangerous HTML, unforma
2729
$name = ucwords(strtolower(trim($request->name)));
2830
$email = strtolower(trim($request->email));
2931
$cpf = preg_replace('/\D/', '', $request->cpf);
30-
$input = htmlspecialchars(strip_tags($request->bio));
32+
$bio = htmlspecialchars(strip_tags($request->bio));
3133

3234
// No record of what changed, no idempotency guarantee,
3335
// no attribute-driven DTOs, no composition.
@@ -58,7 +60,7 @@ $result = $engine->sanitize(
5860
echo $result->get('name'); // "Walmir Silva"
5961
echo $result->get('email'); // "admin@kariricode.org"
6062
echo $result->get('cpf'); // "529.982.247-25"
61-
echo $result->get('bio'); // "&lt;script&gt;alert(...)...Bold"
63+
echo $result->get('bio'); // "&lt;script&gt;...Bold"
6264
```
6365

6466
---
@@ -95,7 +97,7 @@ $result = $engine->sanitize(
9597
data: ['name' => ' walmir SILVA ', 'email' => ' Admin@Example.ORG '],
9698
fieldRules: [
9799
'name' => ['trim', 'normalize_whitespace', 'capitalize'],
98-
'email' => ['trim', 'lower_case'],
100+
'email' => ['trim', 'lower_case', 'email_filter'],
99101
],
100102
);
101103

@@ -109,21 +111,26 @@ echo $result->get('email'); // "admin@example.org"
109111

110112
```php
111113
use KaririCode\Sanitizer\Attribute\Sanitize;
114+
use KaririCode\Sanitizer\Provider\SanitizerServiceProvider;
112115

113116
final class CreateUserRequest
114117
{
115-
#[Sanitize('trim', 'lower_case')]
118+
#[Sanitize('trim', 'lower_case', 'email_filter')]
116119
public string $email = ' User@Test.COM ';
117120

118121
#[Sanitize('trim', 'capitalize')]
119122
public string $name = ' walmir silva ';
120123

121124
#[Sanitize('format_cpf')]
122125
public string $cpf = '52998224725';
126+
127+
#[Sanitize(['truncate', ['max' => 200, 'suffix' => '…']])]
128+
public string $bio = '';
123129
}
124130

125131
$sanitizer = (new SanitizerServiceProvider())->createAttributeSanitizer();
126-
$result = $sanitizer->sanitize(new CreateUserRequest());
132+
$dto = new CreateUserRequest();
133+
$sanitizer->sanitize($dto);
127134

128135
// $dto->email === 'user@test.com'
129136
// $dto->name === 'Walmir Silva'
@@ -149,8 +156,8 @@ $result->modificationCount(); // 2
149156
foreach ($result->modificationsFor('name') as $mod) {
150157
echo "{$mod->ruleName}: '{$mod->before}' → '{$mod->after}'\n";
151158
}
152-
// string.trim: ' Walmir ' → 'Walmir'
153-
// string.upper_case: 'Walmir' → 'WALMIR'
159+
// trim: ' Walmir ' → 'Walmir'
160+
// upper_case: 'Walmir' → 'WALMIR'
154161
```
155162

156163
---
@@ -163,7 +170,8 @@ $result = $engine->sanitize(
163170
['input' => ['strip_tags', 'html_encode']],
164171
);
165172
// Result: "&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;Bold"
166-
// Or with strip_tags alone: 'alert("xss")Bold'
173+
// strip_tags alone: 'alert("xss")Bold'
174+
// html_purify (strip + entity decode + trim): 'Bold'
167175
```
168176

169177
---
@@ -184,38 +192,73 @@ $result = $engine->sanitize(
184192

185193
## All 33 Rules
186194

187-
| Category | Rules | Aliases |
195+
| Category | Count | Aliases |
188196
|---|---|---|
189-
| **String** (12) | Trim, LowerCase, UpperCase, Capitalize, Slug, Truncate, NormalizeWhitespace, NormalizeLineEndings, Pad, Replace, RegexReplace, StripNonPrintable | `trim`, `lower_case`, `upper_case`, `capitalize`, `slug`, `truncate`, `normalize_whitespace`, `normalize_line_endings`, `pad`, `replace`, `regex_replace`, `strip_non_printable` |
190-
| **HTML** (5) | StripTags, HtmlEncode, HtmlDecode, HtmlPurify, UrlEncode | `strip_tags`, `html_encode`, `html_decode`, `html_purify`, `url_encode` |
191-
| **Numeric** (4) | ToInt, ToFloat, Clamp, Round | `to_int`, `to_float`, `clamp`, `round` |
192-
| **Type** (3) | ToBool, ToString, ToArray | `to_bool`, `to_string`, `to_array` |
193-
| **Date** (2) | NormalizeDate, TimestampToDate | `normalize_date`, `timestamp_to_date` |
194-
| **Filter** (4) | DigitsOnly, AlphaOnly, AlphanumericOnly, EmailFilter | `digits_only`, `alpha_only`, `alphanumeric_only`, `email_filter` |
195-
| **Brazilian** (3) | FormatCPF, FormatCNPJ, FormatCEP | `format_cpf`, `format_cnpj`, `format_cep` |
197+
| **String** | 12 | `trim`, `lower_case`, `upper_case`, `capitalize`, `slug`, `truncate`, `normalize_whitespace`, `normalize_line_endings`, `pad`, `replace`, `regex_replace`, `strip_non_printable` |
198+
| **HTML** | 5 | `strip_tags`, `html_encode`, `html_decode`, `html_purify`, `url_encode` |
199+
| **Numeric** | 4 | `to_int`, `to_float`, `clamp`, `round` |
200+
| **Type** | 3 | `to_bool`, `to_string`, `to_array` |
201+
| **Date** | 2 | `normalize_date`, `timestamp_to_date` |
202+
| **Filter** | 4 | `digits_only`, `alpha_only`, `alphanumeric_only`, `email_filter` |
203+
| **Brazilian** | 3 | `format_cpf`, `format_cnpj`, `format_cep` |
204+
205+
See [SPEC-002](docs/spec/SPEC-002-rule-reference.md) for full parameter reference.
196206

197207
---
198208

199-
## Engine API (Programmatic)
209+
## Rule Parameters
200210

201211
```php
202-
$engine = (new SanitizerServiceProvider())->createEngine();
212+
// truncate — max chars + suffix
213+
$engine->sanitize(['bio' => $bio], ['bio' => [['truncate', ['max' => 200, 'suffix' => '…']]]]);
203214

204-
$result = $engine->sanitize(
205-
['html' => '<b>test</b>', 'text' => ' spaces '],
206-
['html' => ['strip_tags', 'trim'], 'text' => ['trim', 'upper_case']],
207-
);
215+
// pad — length, pad char, side ('left'|'right'|'both')
216+
$engine->sanitize(['id' => '7'], ['id' => [['pad', ['length' => 5, 'pad' => '0', 'side' => 'left']]]]);
217+
// → "00007"
208218

209-
$result->get('html'); // "test"
210-
$result->get('text'); // "SPACES"
211-
$result->wasModified(); // true
212-
$result->modifiedFields(); // ['html', 'text']
213-
$result->modificationCount(); // 4
219+
// round — precision and mode ('round'|'ceil'|'floor')
220+
$engine->sanitize(['price' => 9.9], ['price' => [['round', ['precision' => 2]]]]);
214221

215-
foreach ($result->modificationsFor('html') as $mod) {
216-
echo "{$mod->ruleName}: '{$mod->before}' → '{$mod->after}'\n";
222+
// clamp — min and max bounds
223+
$engine->sanitize(['age' => 150], ['age' => [['clamp', ['min' => 0, 'max' => 120]]]]);
224+
225+
// normalize_date — from/to format
226+
$engine->sanitize(['dob' => '25/12/1990'], ['dob' => [['normalize_date', ['from' => 'd/m/Y', 'to' => 'Y-m-d']]]]);
227+
// → "1990-12-25"
228+
```
229+
230+
---
231+
232+
## Custom Rules
233+
234+
```php
235+
use KaririCode\Sanitizer\Contract\SanitizationRule;
236+
use KaririCode\Sanitizer\Contract\SanitizationContext;
237+
238+
final class PhoneRule implements SanitizationRule
239+
{
240+
public function sanitize(mixed $value, SanitizationContext $context): mixed
241+
{
242+
if (!is_string($value)) {
243+
return $value; // ARFA passthrough — do not coerce
244+
}
245+
return preg_replace('/\D/', '', $value) ?? $value;
246+
}
247+
248+
#[\Override]
249+
public function getName(): string
250+
{
251+
return 'phone';
252+
}
217253
}
218-
// html.strip_tags: '<b>test</b>' → 'test'
254+
255+
// Register and use
256+
$registry = (new SanitizerServiceProvider())->createRegistry();
257+
$registry->register('phone', new PhoneRule());
258+
259+
$engine = new SanitizerEngine($registry);
260+
$result = $engine->sanitize(['phone' => '(85) 99999-9999'], ['phone' => ['phone']]);
261+
// → "85999999999"
219262
```
220263

221264
---
@@ -228,7 +271,9 @@ Infra Pipeline: Object ↔ Normalizer ↔ Array ↔ Serializer ↔ String
228271
Cross-Layer: Request DTO ↔ Mapper ↔ Domain Entity ↔ Mapper ↔ Response DTO
229272
```
230273

231-
The Sanitizer **cleans data** — removes noise while preserving semantic meaning. Key property: idempotency — `sanitize(sanitize(x)) = sanitize(x)`. Contrast with the Transformer, which converts representation and may change type.
274+
The Sanitizer **cleans data** — removes noise while preserving semantic meaning.
275+
Key property: idempotency — `sanitize(sanitize(x)) = sanitize(x)`.
276+
Contrast with the Transformer, which converts representation and may change type.
232277

233278
---
234279

@@ -239,15 +284,20 @@ The Sanitizer **cleans data** — removes noise while preserving semantic meanin
239284
```
240285
src/
241286
├── Attribute/ Sanitize — field-level sanitization annotation
242-
├── Contract/ SanitizationRule · SanitizationContext · SanitizerEngine · Modification
287+
├── Configuration/ SanitizerConfiguration
288+
├── Contract/ SanitizationRule · SanitizationContext · RuleRegistry
243289
├── Core/ SanitizerEngine · SanitizationContextImpl · InMemoryRuleRegistry
290+
│ SanitizeAttributeHandler · AttributeSanitizer
291+
├── Event/ SanitizationStartedEvent · SanitizationCompletedEvent
244292
├── Exception/ SanitizationException · InvalidRuleException
245-
├── Provider/ SanitizerServiceProvider — factory for engine & attribute sanitizer
293+
├── Integration/ ProcessorBridge
294+
├── Provider/ SanitizerServiceProvider
295+
├── Result/ SanitizationResult · FieldModification
246296
└── Rule/
247297
├── Brazilian/ FormatCPF · FormatCNPJ · FormatCEP
248298
├── Date/ NormalizeDate · TimestampToDate
249299
├── Filter/ DigitsOnly · AlphaOnly · AlphanumericOnly · EmailFilter
250-
├── HTML/ StripTags · HtmlEncode · HtmlDecode · HtmlPurify · UrlEncode
300+
├── Html/ StripTags · HtmlEncode · HtmlDecode · HtmlPurify · UrlEncode
251301
├── Numeric/ ToInt · ToFloat · Clamp · Round
252302
├── String/ Trim · LowerCase · UpperCase · Capitalize · Slug · Truncate · …
253303
└── Type/ ToBool · ToString · ToArray
@@ -257,33 +307,40 @@ src/
257307

258308
| Decision | Rationale | ADR |
259309
|---|---|---|
260-
| Idempotency guarantee | `sanitize(sanitize(x)) = sanitize(x)` for all rules | [ADR-001](docs/adr/ADR-001-idempotency.md) |
261-
| Modification tracking | Full audit trail without extra overhead | [ADR-002](docs/adr/ADR-002-modification-tracking.md) |
262-
| `final readonly` rules | Immutability, PHPStan L9 | [ADR-003](docs/adr/ADR-003-immutable-rules.md) |
310+
| Alias-based rule registry | Flat names (`trim`), no FQCN coupling, custom aliases | [ADR-001](docs/adr/ADR-001-rule-registry-pattern.md) |
311+
| Property Inspector integration | Delegates reflection and caching to `kariricode/property-inspector` | [ADR-002](docs/adr/ADR-002-property-inspector-integration.md) |
312+
| Immutable `SanitizationContext` | Thread safety, no cross-rule parameter pollution | [ADR-003](docs/adr/ADR-003-sanitization-context-immutability.md) |
313+
| ARFA passthrough contract | Non-matching types returned unchanged — rules never coerce | [ADR-004](docs/adr/ADR-004-arfa-passthrough-contract.md) |
314+
| Zero-dependency rules | All 33 rules use only PHP built-ins | [ADR-005](docs/adr/ADR-005-zero-dependency-rules.md) |
263315

264316
### Specifications
265317

266318
| Spec | Covers |
267319
|---|---|
268-
| [SPEC-001](docs/spec/SPEC-001-sanitization-contract.md) | Rule contract and idempotency |
269-
| [SPEC-002](docs/spec/SPEC-002-modification-tracking.md) | Modification record format |
320+
| [SPEC-001](docs/spec/SPEC-001-sanitizer-engine.md) | Engine contract, sanitize flow, result API |
321+
| [SPEC-002](docs/spec/SPEC-002-rule-reference.md) | All 33 rules — aliases, parameters, defaults |
322+
| [SPEC-003](docs/spec/SPEC-003-attribute-sanitizer.md) | `#[Sanitize]` attribute shape and DTO flow |
270323

271324
---
272325

273326
## Project Stats
274327

275328
| Metric | Value |
276329
|---|---|
277-
| PHP source files | 50 |
278-
| Source lines | 1,913 |
279-
| Test files | 15 |
280-
| Test lines | 969 |
281-
| External runtime dependencies | 1 (kariricode/property-inspector) |
330+
| PHP source files | 51 |
331+
| Source lines | ~2,100 |
332+
| Test files | 20 |
333+
| Test lines | ~1,938 |
334+
| Tests | 175 passing |
335+
| Assertions | 425 |
336+
| Coverage | 100% (48 classes) |
337+
| External runtime dependencies | 1 (`kariricode/property-inspector`) |
282338
| Rule classes | 33 |
283339
| Rule categories | 7 |
284-
| PHPStan level | 9 |
340+
| PHPStan level | 9 (0 errors) |
341+
| Psalm | 100% type inference (0 errors) |
285342
| PHP version | 8.4+ |
286-
| ARFA compliance | 1.3 |
343+
| ARFA compliance | 1.43 V4.0 |
287344

288345
---
289346

0 commit comments

Comments
 (0)