Skip to content

Commit 145111b

Browse files
committed
Deep refactor of schema for 2.0.0 - plan ready
1 parent b0373bc commit 145111b

File tree

2 files changed

+598
-0
lines changed

2 files changed

+598
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Symfony TypeInfo Spike for Schema2
2+
3+
Date: 2026-03-01
4+
Context: Keep composer compatibility as `^7 || ^8`, but validate design using Symfony 8 TypeInfo.
5+
6+
## Objective
7+
8+
Verify whether `symfony/type-info` can replace `TypeDetails` as the core internal type engine, and identify exactly what is still missing for full replacement.
9+
10+
## Environment
11+
12+
- PHP: `8.5.1`
13+
- `symfony/type-info`: `8.0.6`
14+
- Composer constraints remain 7/8-compatible in `composer.json`.
15+
16+
## Experiments Run
17+
18+
## 1) Property + phpdoc resolution via TypeResolver
19+
20+
Used `TypeResolver::create()->resolve(new ReflectionProperty(...))` on:
21+
22+
- scalar property (`int`)
23+
- nullable scalar (`?int`)
24+
- backed enum
25+
- list with union item type (`list<int|float>`)
26+
- generic map (`array<string, Enum>`)
27+
- array shape (`array{street: string, zip?: int}`)
28+
- scalar unions (`int|float`, `int|string`)
29+
30+
Result:
31+
32+
- TypeInfo resolved all of these correctly.
33+
- Returned rich types (`NullableType`, `BackedEnumType`, `CollectionType`, `ArrayShapeType`, `UnionType`) with structured accessors.
34+
35+
## 2) Raw type-string parsing via TypeResolver
36+
37+
Resolved strings:
38+
39+
- `int|float`
40+
- `int|string`
41+
- `?int`
42+
- `array<string, Enum>`
43+
- `list<int|float>`
44+
- `array{street: string, zip?: int}`
45+
- `Foo[]`, `int[]`, `array<int>`
46+
47+
Result:
48+
49+
- Works and is stronger than current `TypeDetails::fromTypeName` parser.
50+
- Handles array shapes and generic array/list forms that currently fail in `TypeDetails`.
51+
52+
## 3) Compare current `TypeDetails::fromTypeName` behavior
53+
54+
Current behavior highlights:
55+
56+
- `int|float` -> collapses to `float`
57+
- `int|string` -> collapses to `mixed`
58+
- `array<string, Enum>` -> unsupported
59+
- `list<int|float>` -> unsupported
60+
- `array{...}` -> unsupported
61+
62+
Result:
63+
64+
- Current `TypeDetails` parser is intentionally lossy / limited.
65+
- TypeInfo provides better structural fidelity.
66+
67+
## 4) Adapter feasibility check (TypeInfo -> TypeDetails)
68+
69+
Built a small prototype converter script mapping:
70+
71+
- `BuiltinType` -> scalar/mixed/array
72+
- `NullableType` -> wrapped type + nullability handled outside type identity
73+
- `BackedEnumType` -> `TypeDetails::enum(...)`
74+
- `CollectionType` -> `TypeDetails::collection(...)` / array fallback
75+
- `UnionType` -> existing compatibility policy (`int|float -> float`, others -> mixed)
76+
- `ArrayShapeType` -> array fallback
77+
78+
Result:
79+
80+
- Feasible for current runtime semantics.
81+
- Confirms we can make TypeInfo internal while preserving `TypeDetails` compatibility behavior.
82+
83+
## What TypeInfo Covers Well (for us)
84+
85+
1. Reflection + phpdoc type extraction with modern typing features.
86+
2. Nullable, union, enum, collection, array-shape modeling.
87+
3. Generic collection key/value inspection.
88+
4. Strong typed API for type traversal/inspection.
89+
90+
## Gaps / Missing Pieces vs full `TypeDetails` replacement
91+
92+
1. Enum value list metadata is not directly exposed as first-class value constraints.
93+
- TypeInfo gives enum class + backing type (`BackedEnumType`), but not direct "allowed values" list object.
94+
- Workaround: derive from enum reflection (`::cases()`).
95+
96+
2. "Option" type (string/int enums without enum class) is not a direct TypeInfo concept.
97+
- TypeInfo parses literals, but value lists are not preserved as a dedicated constraint model for our current `enumValues` usage.
98+
99+
3. JSON Schema conversion helpers are not built into TypeInfo.
100+
- We still need schema-layer mapping logic (`JsonSchema` <-> internal type model).
101+
102+
4. Current compatibility policies are domain-specific, not TypeInfo defaults.
103+
- Examples: `int|float -> float`, `int|string -> mixed`, nullable treatment, fallback rules.
104+
- Must stay in our mapping policy layer.
105+
106+
5. Stable serialized type metadata format is ours.
107+
- `TypeDetails::toArray()` style payload currently acts as compatibility DTO in several flows.
108+
- TypeInfo objects are not a drop-in serialized contract for that.
109+
110+
6. Some `fromValue` semantics differ.
111+
- TypeInfo distinguishes `true`/`false` literal bool types.
112+
- We currently normalize to `bool`.
113+
- Requires normalization in adapter.
114+
115+
## Feasibility Verdict
116+
117+
## Can we replace `TypeDetails` entirely in 2.0?
118+
119+
Not safely without broad downstream refactors.
120+
121+
## Can we replace `TypeDetails` internals with TypeInfo now?
122+
123+
Yes. This is a strong and practical path.
124+
125+
Recommended 2.0 approach:
126+
127+
- **TypeInfo as internal canonical resolver**
128+
- **TypeDetails as compatibility DTO/API**
129+
- Conversion adapter enforces legacy compatibility policies
130+
131+
This gives major simplification while avoiding high-risk breakage.
132+
133+
## Proposed Implementation Path
134+
135+
### Phase A: Add adapter layer in schema2
136+
137+
- Introduce `TypeInfoToTypeDetailsMapper` (or equivalent name).
138+
- Centralize policy rules (union collapse, bool normalization, collection narrowing, fallback behavior).
139+
140+
### Phase B: Switch reflection entrypoints
141+
142+
- In `Reflection\PropertyInfo` and related paths, resolve via TypeInfo directly.
143+
- Stop string-roundtripping where possible.
144+
- Keep returning `TypeDetails` for compatibility callsites.
145+
146+
### Phase C: Switch factory internals to TypeInfo-first
147+
148+
- `TypeDetailsFactory` becomes a compatibility factory over TypeInfo + policy.
149+
- Minimize custom parsing logic that duplicates TypeInfo capabilities.
150+
151+
### Phase D: Evaluate post-2.0 type contract cleanup
152+
153+
- After downstream imports shrink, evaluate replacing `TypeDetails` public API with thinner descriptor model.
154+
155+
## Minimum Regression Test Additions
156+
157+
1. Reflection + phpdoc:
158+
- list/map/array-shape/nullable/union/backed enum cases.
159+
160+
2. Compatibility behavior:
161+
- `int|float -> float`
162+
- `int|string -> mixed`
163+
- bool literal normalization (`true`/`false` -> bool where expected).
164+
165+
3. Enum metadata:
166+
- backed enum class + backing type + values list parity where required by JSON schema rendering.
167+
168+
4. JSON-schema roundtrip:
169+
- no regressions in current schema generation/parsing tests.
170+
171+
## Final Recommendation
172+
173+
Proceed with **TypeInfo-first internals now**, keep `TypeDetails` as 2.0 compatibility surface, and defer full public replacement until downstream coupling is reduced.
174+
175+
This achieves the simplification objective without destabilizing runtime packages.

0 commit comments

Comments
 (0)