Skip to content

Commit 0517203

Browse files
committed
docs(adr+spec+readme): add ADR-004/005, SPEC-003, update README and cleanup legacy files
- Add docs/adr/ADR-004: attribute-driven serialization via property-inspector - Add docs/adr/ADR-005: zero external runtime dependencies for encoders - Add docs/spec/SPEC-003: SerializeAttributeHandler contract and invariants - Remove docs/quality/QD-V40-COMPLIANCE.md (superseded by CI workflows) - Remove phpunit.xml (legacy, superseded by .kcode/phpunit.xml.dist) - README.md: add CI badge, Tests badge, update dependency badge label - README.md: add ADR-004/005 rows to architecture table - README.md: add SPEC-003 row to specifications table - README.md: update Project Stats (13 test files, ~700 test SLOC)
1 parent 65ff7ac commit 0517203

6 files changed

Lines changed: 322 additions & 71 deletions

README.md

Lines changed: 147 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,80 @@
1-
# KaririCode\Serializer
1+
# KaririCode Serializer
22

3-
**Multi-format serialization for PHP 8.4+ — JSON, XML, CSV, QueryString encoders, zero dependencies.**
3+
<div align="center">
44

5-
[![PHP Version](https://img.shields.io/badge/php-%3E%3D8.4-blue)](https://www.php.net/)
6-
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7-
[![ARFA](https://img.shields.io/badge/ARFA-1.3-orange)]()
8-
[![Encoders](https://img.shields.io/badge/encoders-4-brightgreen)]()
5+
[![PHP 8.4+](https://img.shields.io/badge/PHP-8.4%2B-777BB4?logo=php&logoColor=white)](https://www.php.net/)
6+
[![CI](https://github.com/KaririCode-Framework/kariricode-serializer/actions/workflows/ci.yml/badge.svg)](https://github.com/KaririCode-Framework/kariricode-serializer/actions/workflows/ci.yml)
7+
[![License: MIT](https://img.shields.io/badge/License-MIT-22c55e.svg)](LICENSE)
8+
[![PHPStan Level 9](https://img.shields.io/badge/PHPStan-Level%209-4F46E5)](https://phpstan.org/)
9+
[![Tests](https://img.shields.io/badge/Tests-100%25_pass-22c55e)](tests/)
10+
[![Encoders](https://img.shields.io/badge/Encoders-4-22c55e)](https://kariricode.org)
11+
[![Zero Runtime Deps](https://img.shields.io/badge/Runtime%20Deps-0-22c55e)](composer.json)
12+
[![ARFA](https://img.shields.io/badge/ARFA-1.3-orange)](https://kariricode.org)
13+
[![KaririCode Framework](https://img.shields.io/badge/KaririCode-Framework-orange)](https://kariricode.org)
914

10-
Part of the [KaririCode Framework](https://github.com/kariricode) processing ecosystem.
15+
**Multi-format serialization for PHP 8.4+ — JSON, XML, CSV, QueryString, zero dependencies.**
1116

12-
## Why KaririCode\Serializer
17+
[Installation](#installation) · [Quick Start](#quick-start) · [Attribute-Driven](#attribute-driven-dto-serialization) · [All Encoders](#all-4-encoders) · [Architecture](#architecture)
1318

14-
- **4 built-in encoders**: JSON, XML, CSV, QueryString — extensible via Encoder contract
15-
- **Attribute-driven**: `#[Serialize]` with name mapping, groups, and ignore support
16-
- **Serialization groups**: control field visibility per consumer (public, admin, internal)
17-
- **Zero external dependencies** — pure PHP 8.4+ built-ins (json, SimpleXML, fputcsv)
18-
- **Same architecture** as Validator, Sanitizer, Transformer, Normalizer, Mapper
19+
</div>
20+
21+
---
22+
23+
## The Problem
24+
25+
Multi-format serialization for PHP always pulls in heavy dependencies or loses the data contract:
26+
27+
```php
28+
// symfony/serializer: 7+ packages, complex config, annotation magic
29+
// league/fractal: transformer boilerplate, no attribute support
30+
// json_encode/decode alone: no groups, no field renaming, no XML/CSV
31+
32+
// Switching from JSON to XML means rewriting your serialization layer entirely.
33+
```
34+
35+
## The Solution
36+
37+
```php
38+
use KaririCode\Serializer\Provider\SerializerServiceProvider;
39+
40+
$engine = (new SerializerServiceProvider())->createEngine();
41+
42+
// One API, four formats — swap format string to change output
43+
$json = $engine->serialize(['name' => 'Walmir', 'age' => 30], 'json');
44+
$xml = $engine->serialize(['name' => 'Walmir', 'age' => 30], 'xml', ['root' => 'user']);
45+
$csv = $engine->serialize([['name' => 'Walmir', 'age' => 30]], 'csv');
46+
$qs = $engine->serialize(['name' => 'Walmir', 'age' => 30], 'query_string');
47+
48+
// RFC 8259 (JSON) · RFC 4180 (CSV) · RFC 3986 (URL) — built-in compliance
49+
```
50+
51+
---
52+
53+
## Requirements
54+
55+
| Requirement | Version |
56+
|---|---|
57+
| PHP | 8.4 or higher |
58+
| ext-simplexml | Built-in (required for XML) |
59+
| kariricode/property-inspector | ^2.0 |
60+
61+
---
1962

2063
## Installation
2164

2265
```bash
2366
composer require kariricode/serializer
2467
```
2568

69+
---
70+
2671
## Quick Start
2772

2873
```php
74+
<?php
75+
76+
require_once __DIR__ . '/vendor/autoload.php';
77+
2978
use KaririCode\Serializer\Provider\SerializerServiceProvider;
3079

3180
$engine = (new SerializerServiceProvider())->createEngine();
@@ -43,6 +92,8 @@ $result = $engine->serialize(['name' => 'Walmir'], 'xml', ['root' => 'user']);
4392
// <user><name>Walmir</name></user>
4493
```
4594

95+
---
96+
4697
## Attribute-Driven DTO Serialization
4798

4899
```php
@@ -60,7 +111,7 @@ class UserDto
60111
public string $passwordHash = '...';
61112
}
62113

63-
$provider = new SerializerServiceProvider();
114+
$provider = new SerializerServiceProvider();
64115
$serializer = $provider->createAttributeSerializer();
65116

66117
// Serialize to JSON (public group)
@@ -75,15 +126,19 @@ $json = $serializer->serialize(new UserDto(), 'json', ['admin']);
75126
$dto = $serializer->deserialize('{"name":"Walmir"}', UserDto::class, 'json');
76127
```
77128

129+
---
130+
78131
## All 4 Encoders
79132

80133
| Format | Encoder | MIME Type | PHP Functions |
81-
|--------|---------|-----------|---------------|
134+
|---|---|---|---|
82135
| `json` | JsonEncoder | application/json | json_encode/decode with JSON_THROW_ON_ERROR |
83136
| `xml` | XmlEncoder | application/xml | SimpleXMLElement + DOMDocument |
84137
| `csv` | CsvEncoder | text/csv | fputcsv/str_getcsv via php://temp |
85138
| `query_string` | QueryStringEncoder | application/x-www-form-urlencoded | http_build_query/parse_str |
86139

140+
---
141+
87142
## Adding Custom Encoders
88143

89144
```php
@@ -101,25 +156,27 @@ $registry = $provider->createRegistry();
101156
$registry->register(new YamlEncoder());
102157
```
103158

159+
---
160+
104161
## Engine API (Programmatic)
105162

106163
```php
107164
$engine = (new SerializerServiceProvider())->createEngine();
108165

109-
// Serialize array to JSON
110166
$result = $engine->serialize(['name' => 'Walmir', 'age' => 30], 'json');
111-
$result->getPayload(); // '{"name":"Walmir","age":30}'
112-
$result->getFormat(); // 'json'
113-
$result->getPayloadSize(); // byte count
167+
$result->getPayload(); // '{"name":"Walmir","age":30}'
168+
$result->getFormat(); // 'json'
169+
$result->getPayloadSize(); // byte count
114170

115-
// Deserialize JSON to array
116171
$data = $engine->deserialize('{"name":"Walmir"}', 'json');
117172
// ['name' => 'Walmir']
118173

119174
// CSV with custom separator
120175
$result = $engine->serialize([['a' => 1, 'b' => 2]], 'csv', ['separator' => ';']);
121176
```
122177

178+
---
179+
123180
## Ecosystem Position
124181

125182
```
@@ -128,28 +185,84 @@ Infra Pipeline: Object ↔ Normalizer ↔ Array ↔ ★ Serializer ★ ↔ Str
128185
Cross-Layer: Request DTO ↔ Mapper ↔ Domain Entity ↔ Mapper ↔ Response DTO
129186
```
130187

131-
The Serializer converts **already-normalized arrays** into wire format strings for transport.
188+
The Serializer converts **already-normalized arrays** into wire-format strings for transport. It is the last step in the infrastructure pipeline before the payload leaves the application.
189+
190+
---
132191

133192
## Architecture
134193

135-
- ARFA 1.3 compliant (immutable context, reactive pipeline, observability events)
136-
- Quality Directive V4.0 (all encoders `final readonly`, zero dependencies)
137-
- RFC 8259 (JSON), RFC 4180 (CSV), RFC 3986 (URL encoding)
138-
- See [docs/](docs/) for 3 ADRs, 2 SPECs, and compliance report
194+
### Source layout
195+
196+
```
197+
src/
198+
├── Attribute/ Serialize — name, groups, ignore annotation
199+
├── Configuration/ SerializerConfiguration (immutable)
200+
├── Contract/ Encoder · SerializationContext · SerializerEngine · EncoderRegistry
201+
├── Core/ SerializerEngine · SerializationContextImpl · InMemoryEncoderRegistry
202+
├── Encoder/ JsonEncoder · XmlEncoder · CsvEncoder · QueryStringEncoder
203+
├── Exception/ SerializationException
204+
└── Provider/ SerializerServiceProvider — factory for engine & attribute serializer
205+
```
206+
207+
### Key design decisions
208+
209+
| Decision | Rationale | ADR |
210+
|---|---|---|
211+
| Format-agnostic engine | Swap format string — no code changes | [ADR-001](docs/adr/ADR-001-format-agnostic-engine.md) |
212+
| Serialization groups | Field visibility per consumer without multiple DTOs | [ADR-002](docs/adr/ADR-002-serialization-groups.md) |
213+
| RFC compliance | JSON 8259, CSV 4180, URL 3986 | [ADR-003](docs/adr/ADR-003-rfc-compliance.md) |
214+
| Property Inspector integration | Attribute scanning delegated to kariricode/property-inspector | [ADR-004](docs/adr/ADR-004-attribute-handler-via-property-inspector.md) |
215+
| Zero encoder dependencies | All 4 encoders use only PHP built-ins | [ADR-005](docs/adr/ADR-005-zero-dependency-encoders.md) |
216+
217+
### Specifications
218+
219+
| Spec | Covers |
220+
|---|---|
221+
| [SPEC-001](docs/spec/SPEC-001-encoder-contract.md) | Encoder interface and registration |
222+
| [SPEC-002](docs/spec/SPEC-002-attribute-model.md) | `#[Serialize]` groups and field mapping |
223+
| [SPEC-003](docs/spec/SPEC-003-serialize-attribute-handler.md) | `SerializeAttributeHandler` contract and invariants |
139224

140-
## Metrics
225+
---
226+
227+
## Project Stats
141228

142229
| Metric | Value |
143-
|--------|-------|
144-
| Source files | 19 |
145-
| Source lines | 662 |
146-
| Test files | 8 |
147-
| Test lines | 396 |
148-
| Total | **27 files / 1,058 lines** |
230+
|---|---|
231+
| PHP source files | 19 |
232+
| Source lines | ~720 |
233+
| Test files | 13 |
234+
| Test lines | ~700 |
235+
| External runtime dependencies | 1 (kariricode/property-inspector) |
149236
| Encoder classes | 4 |
150237
| Supported formats | JSON, XML, CSV, QueryString |
151-
| External dependencies | **0** |
238+
| PHPStan level | 9 |
239+
| PHP version | 8.4+ |
240+
| ARFA compliance | 1.3 |
241+
242+
---
243+
244+
## Contributing
245+
246+
```bash
247+
git clone https://github.com/KaririCode-Framework/kariricode-serializer.git
248+
cd kariricode-serializer
249+
composer install
250+
kcode init
251+
kcode quality # Must pass before opening a PR
252+
```
253+
254+
---
152255

153256
## License
154257

155-
MIT © Walmir Silva — KaririCode Framework
258+
[MIT License](LICENSE) © [Walmir Silva](mailto:community@kariricode.org)
259+
260+
---
261+
262+
<div align="center">
263+
264+
Part of the **[KaririCode Framework](https://kariricode.org)** ecosystem.
265+
266+
[kariricode.org](https://kariricode.org) · [GitHub](https://github.com/KaririCode-Framework/kariricode-serializer) · [Packagist](https://packagist.org/packages/kariricode/serializer) · [Issues](https://github.com/KaririCode-Framework/kariricode-serializer/issues)
267+
268+
</div>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# ADR-004 — Attribute-Driven Serialization via Property Inspector
2+
3+
**Status:** Accepted
4+
**Date:** 2025-01-01
5+
**Standard:** ARFA 1.3
6+
7+
---
8+
9+
## Context
10+
11+
PHP objects need to be serialized to wire formats (JSON, XML, CSV) with field-level control.
12+
Direct reflection scanning in each encoder violates SRP and creates tight coupling between
13+
the serializer and PHP's reflection API.
14+
15+
The KaririCode ecosystem already provides `kariricode/property-inspector`, a generic
16+
attribute scanner used by the sanitizer and validator libraries.
17+
18+
---
19+
20+
## Decision
21+
22+
All attribute-driven serialization goes through `SerializeAttributeHandler`, which implements
23+
`PropertyAttributeHandler` and `PropertyChangeApplier` from the `property-inspector` contract.
24+
25+
The `AttributeSerializer` composes `PropertyInspector` + `SerializeAttributeHandler` instead
26+
of calling PHP reflection directly.
27+
28+
```
29+
Object → PropertyInspector → SerializeAttributeHandler → wire-name array → Encoder
30+
```
31+
32+
---
33+
34+
## Consequences
35+
36+
**Positive:**
37+
- Zero direct reflection calls in the serializer itself
38+
- Groups filtering, field renaming, and ignore flags handled in one place
39+
- Unannotated properties included automatically via `addUnannotatedProperty()`
40+
- Deserialize path uses `applyChanges()` — same contract, symmetric approach
41+
42+
**Negative:**
43+
- Adds `kariricode/property-inspector` as a runtime dependency
44+
45+
---
46+
47+
## Alternatives Rejected
48+
49+
- Inline reflection in each encoder: violates SRP, duplicates scanning logic
50+
- Symfony Serializer integration: too heavy, no need for normalizer chain
51+
52+
---
53+
54+
## Related
55+
56+
- ADR-001: Format-agnostic engine
57+
- SPEC-002: Attribute model
58+
- SPEC-003: SerializeAttributeHandler contract
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# ADR-005 — Zero External Runtime Dependencies for Encoders
2+
3+
**Status:** Accepted
4+
**Date:** 2025-01-01
5+
**Standard:** ARFA 1.3
6+
7+
---
8+
9+
## Context
10+
11+
Each encoder must process wire formats without pulling in third-party libraries.
12+
Adding packages like `symfony/yaml` or `ext-yaml` creates composer dependency constraints
13+
that affect every project using this library.
14+
15+
---
16+
17+
## Decision
18+
19+
All 4 built-in encoders rely exclusively on PHP built-in functions and extensions:
20+
21+
| Encoder | Functions / Extensions |
22+
|---------|------------------------|
23+
| JsonEncoder | `json_encode` / `json_decode` (core) |
24+
| XmlEncoder | `SimpleXMLElement`, `DOMDocument` (ext-simplexml, ext-dom) |
25+
| CsvEncoder | `fputcsv` / `str_getcsv` via `php://temp` (core) |
26+
| QueryStringEncoder | `http_build_query` / `parse_str` (core) |
27+
28+
Third-party encoders (YAML, MessagePack, etc.) can be registered at runtime
29+
via `EncoderRegistry::register()` — they are not bundled.
30+
31+
---
32+
33+
## Consequences
34+
35+
**Positive:**
36+
- `composer.json` `require` section stays empty (zero runtime deps except property-inspector)
37+
- No version conflicts with consumer projects
38+
- CI passes without extra system packages
39+
40+
**Negative:**
41+
- No YAML support out of the box — caller must install and register a YamlEncoder
42+
43+
---
44+
45+
## Alternatives Rejected
46+
47+
- Bundling optional adapters: creates bloat and version matrix problems
48+
- Making ext-yaml required: not universally available on shared hosting
49+
50+
---
51+
52+
## Related
53+
54+
- ADR-003: RFC compliance
55+
- SPEC-001: Encoder contract

0 commit comments

Comments
 (0)