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(
5860echo $result->get('name'); // "Walmir Silva"
5961echo $result->get('email'); // "admin@kariricode.org"
6062echo $result->get('cpf'); // "529.982.247-25"
61- echo $result->get('bio'); // "< ; script> ; alert(...) ...Bold"
63+ echo $result->get('bio'); // "< ; script> ; ...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
111113use KaririCode\Sanitizer\Attribute\Sanitize;
114+ use KaririCode\Sanitizer\Provider\SanitizerServiceProvider;
112115
113116final 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
149156foreach ($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: "< ; script> ; alert(" ; xss" ; )< ; /script> ; 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
228271Cross-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```
240285src/
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