Skip to content

Commit f2136bf

Browse files
committed
feat(phaseF): add explicit adapter factories and infrastructure error boundary
- Introduced MySQL, Redis, and MongoDB adapter factories - Added AdapterCreationException as a typed infrastructure boundary - Enforced callable():Driver factory contracts (PHPStan level max) - No env access, auto-detection, or adapter-side logic added
1 parent 8beee21 commit f2136bf

7 files changed

Lines changed: 610 additions & 0 deletions

File tree

docs/api-map.json

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,275 @@
162162
}
163163
}
164164
}
165+
},
166+
{
167+
"version": "2.0.0",
168+
"package": "maatify/data-adapters",
169+
170+
"public_api": {
171+
"interfaces": {
172+
"Maatify\\Common\\Contracts\\Adapter\\AdapterInterface": {
173+
"origin": "maatify/common",
174+
"type": "interface",
175+
"template": "TDriver of object",
176+
"methods": {
177+
"getDriver": {
178+
"visibility": "public",
179+
"args": [],
180+
"return": "object",
181+
"docblock_return": "TDriver",
182+
"notes": [
183+
"Returns the owned concrete driver instance",
184+
"Acts strictly as a DI boundary",
185+
"Does not unify or proxy driver APIs"
186+
]
187+
}
188+
}
189+
}
190+
},
191+
192+
"adapters": {
193+
"Maatify\\DataAdapters\\Adapters\\MySQL\\MySQLPDOAdapter": {
194+
"type": "class",
195+
"implements": [
196+
"Maatify\\Common\\Contracts\\Adapter\\AdapterInterface<PDO>"
197+
],
198+
"constructor": {
199+
"visibility": "public",
200+
"args": [
201+
{
202+
"name": "driver",
203+
"type": "PDO"
204+
}
205+
]
206+
},
207+
"methods": {
208+
"getDriver": {
209+
"visibility": "public",
210+
"args": [],
211+
"return": "object"
212+
}
213+
}
214+
},
215+
216+
"Maatify\\DataAdapters\\Adapters\\MySQL\\MySQLDBALAdapter": {
217+
"type": "class",
218+
"implements": [
219+
"Maatify\\Common\\Contracts\\Adapter\\AdapterInterface<Doctrine\\DBAL\\Connection>"
220+
],
221+
"constructor": {
222+
"visibility": "public",
223+
"args": [
224+
{
225+
"name": "driver",
226+
"type": "Doctrine\\DBAL\\Connection"
227+
}
228+
]
229+
},
230+
"methods": {
231+
"getDriver": {
232+
"visibility": "public",
233+
"args": [],
234+
"return": "object"
235+
}
236+
}
237+
},
238+
239+
"Maatify\\DataAdapters\\Adapters\\Redis\\RedisAdapter": {
240+
"type": "class",
241+
"implements": [
242+
"Maatify\\Common\\Contracts\\Adapter\\AdapterInterface<Redis>"
243+
],
244+
"constructor": {
245+
"visibility": "public",
246+
"args": [
247+
{
248+
"name": "driver",
249+
"type": "Redis"
250+
}
251+
]
252+
},
253+
"methods": {
254+
"getDriver": {
255+
"visibility": "public",
256+
"args": [],
257+
"return": "object"
258+
}
259+
}
260+
},
261+
262+
"Maatify\\DataAdapters\\Adapters\\Redis\\RedisPredisAdapter": {
263+
"type": "class",
264+
"implements": [
265+
"Maatify\\Common\\Contracts\\Adapter\\AdapterInterface<Predis\\Client>"
266+
],
267+
"constructor": {
268+
"visibility": "public",
269+
"args": [
270+
{
271+
"name": "driver",
272+
"type": "Predis\\Client"
273+
}
274+
]
275+
},
276+
"methods": {
277+
"getDriver": {
278+
"visibility": "public",
279+
"args": [],
280+
"return": "object"
281+
}
282+
}
283+
},
284+
285+
"Maatify\\DataAdapters\\Adapters\\Mongo\\MongoDatabaseAdapter": {
286+
"type": "class",
287+
"implements": [
288+
"Maatify\\Common\\Contracts\\Adapter\\AdapterInterface<MongoDB\\Database>"
289+
],
290+
"constructor": {
291+
"visibility": "public",
292+
"args": [
293+
{
294+
"name": "driver",
295+
"type": "MongoDB\\Database"
296+
}
297+
]
298+
},
299+
"methods": {
300+
"getDriver": {
301+
"visibility": "public",
302+
"args": [],
303+
"return": "object"
304+
}
305+
}
306+
}
307+
},
308+
309+
"factories": {
310+
"Maatify\\DataAdapters\\Factories\\MySQLAdapterFactory": {
311+
"type": "class",
312+
"methods": {
313+
"fromPDO": {
314+
"visibility": "public",
315+
"args": [
316+
{
317+
"name": "pdo",
318+
"type": "PDO"
319+
}
320+
],
321+
"return": "Maatify\\DataAdapters\\Adapters\\MySQL\\MySQLPDOAdapter"
322+
},
323+
"fromDBAL": {
324+
"visibility": "public",
325+
"args": [
326+
{
327+
"name": "connection",
328+
"type": "Doctrine\\DBAL\\Connection"
329+
}
330+
],
331+
"return": "Maatify\\DataAdapters\\Adapters\\MySQL\\MySQLDBALAdapter"
332+
},
333+
"fromPDOFactory": {
334+
"visibility": "public",
335+
"args": [
336+
{
337+
"name": "pdoFactory",
338+
"type": "callable():PDO",
339+
"static_enforced": true
340+
}
341+
],
342+
"return": "Maatify\\DataAdapters\\Adapters\\MySQL\\MySQLPDOAdapter",
343+
"notes": [
344+
"Deferred PDO creation via callable",
345+
"Callable return type enforced by PHPStan"
346+
]
347+
}
348+
}
349+
},
350+
351+
"Maatify\\DataAdapters\\Factories\\RedisAdapterFactory": {
352+
"type": "class",
353+
"methods": {
354+
"fromRedis": {
355+
"visibility": "public",
356+
"args": [
357+
{
358+
"name": "redis",
359+
"type": "Redis"
360+
}
361+
],
362+
"return": "Maatify\\DataAdapters\\Adapters\\Redis\\RedisAdapter"
363+
},
364+
"fromPredis": {
365+
"visibility": "public",
366+
"args": [
367+
{
368+
"name": "client",
369+
"type": "Predis\\Client"
370+
}
371+
],
372+
"return": "Maatify\\DataAdapters\\Adapters\\Redis\\RedisPredisAdapter"
373+
},
374+
"fromRedisFactory": {
375+
"visibility": "public",
376+
"args": [
377+
{
378+
"name": "redisFactory",
379+
"type": "callable():Redis",
380+
"static_enforced": true
381+
}
382+
],
383+
"return": "Maatify\\DataAdapters\\Adapters\\Redis\\RedisAdapter"
384+
}
385+
}
386+
},
387+
388+
"Maatify\\DataAdapters\\Factories\\MongoAdapterFactory": {
389+
"type": "class",
390+
"methods": {
391+
"fromDatabase": {
392+
"visibility": "public",
393+
"args": [
394+
{
395+
"name": "database",
396+
"type": "MongoDB\\Database"
397+
}
398+
],
399+
"return": "Maatify\\DataAdapters\\Adapters\\Mongo\\MongoDatabaseAdapter"
400+
},
401+
"fromDatabaseFactory": {
402+
"visibility": "public",
403+
"args": [
404+
{
405+
"name": "databaseFactory",
406+
"type": "callable():MongoDB\\Database",
407+
"static_enforced": true
408+
}
409+
],
410+
"return": "Maatify\\DataAdapters\\Adapters\\Mongo\\MongoDatabaseAdapter"
411+
}
412+
}
413+
}
414+
},
415+
416+
"exceptions": {
417+
"Maatify\\DataAdapters\\Exceptions\\AdapterCreationException": {
418+
"type": "exception",
419+
"extends": "RuntimeException",
420+
"purpose": "Typed infrastructure boundary for adapter factory failures"
421+
}
422+
}
423+
},
424+
425+
"explicitly_absent": [
426+
"Environment variable access",
427+
"Runtime auto-detection or switching",
428+
"Unified database or cache APIs",
429+
"Repository or query abstractions",
430+
"Connection pooling or health checks",
431+
"Retry, fallback, or failover logic"
432+
]
165433
}
434+
166435
]
167436
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Phase F — Factories & Infrastructure Error Boundary
2+
3+
## Version
4+
v2.0.0
5+
6+
## Status
7+
COMPLETED
8+
9+
---
10+
11+
## 🎯 Phase Goal
12+
13+
Introduce an **explicit factory layer** for creating adapters while enforcing a
14+
**clear infrastructure error boundary**, without introducing:
15+
16+
- Environment access
17+
- Runtime auto-detection
18+
- Hidden defaults
19+
- Adapter-side logic
20+
21+
Factories exist **only as a convenience layer** and are not part of the core adapter contract.
22+
23+
---
24+
25+
## 🧱 What Was Added
26+
27+
### 1) Adapter Factories
28+
29+
Factories were introduced for each supported infrastructure:
30+
31+
#### MySQL
32+
- `MySQLAdapterFactory`
33+
- `fromPDO(PDO $pdo)`
34+
- `fromDBAL(Connection $connection)`
35+
- `fromPDOFactory(callable():PDO)`
36+
37+
#### Redis
38+
- `RedisAdapterFactory`
39+
- `fromRedis(Redis $redis)`
40+
- `fromPredis(Client $client)`
41+
- `fromRedisFactory(callable():Redis)`
42+
43+
#### MongoDB
44+
- `MongoAdapterFactory`
45+
- `fromDatabase(Database $database)`
46+
- `fromDatabaseFactory(callable():Database)`
47+
48+
All factories:
49+
- Are fully explicit
50+
- Perform no auto-detection
51+
- Do not read env or config files
52+
- Do not unify driver APIs
53+
54+
---
55+
56+
### 2) Infrastructure Error Boundary
57+
58+
A typed exception was introduced:
59+
60+
- `AdapterCreationException`
61+
62+
Responsibilities:
63+
- Wrap vendor exceptions thrown during factory execution
64+
- Act as a **clear boundary** between application code and infrastructure creation
65+
- Prevent leakage of vendor-specific exceptions into higher layers
66+
67+
Adapters themselves remain exception-transparent and logic-free.
68+
69+
---
70+
71+
## 🧠 Design Decisions
72+
73+
### Why factories accept `callable():Driver`
74+
75+
- Enables deferred construction
76+
- Keeps factories deterministic
77+
- Avoids passing configuration or env into the adapter layer
78+
- Fully compatible with PHPStan level max via `@phpstan-param`
79+
80+
### Why no `instanceof` checks
81+
82+
- Static analysis guarantees the callable contract
83+
- Runtime validation would be redundant and misleading
84+
- Contract violations are considered application-level bugs
85+
86+
---
87+
88+
## 🚫 Explicitly Out of Scope
89+
90+
This phase does NOT include:
91+
92+
- `fromEnv()` helpers
93+
- Configuration DTO handling
94+
- Runtime driver validation
95+
- Connection health checks
96+
- Retry, fallback, or pooling logic
97+
98+
---
99+
100+
## ✅ Completion Criteria
101+
102+
- All factories are explicit and deterministic
103+
- PHPStan level max passes with zero errors
104+
- No architectural drift from DI-first principles
105+
- No changes to adapter contracts
106+
107+
---
108+
109+
## 🔒 Phase Status
110+
111+
**CLOSED**
112+
113+
Next phase: **Phase G — Testing Strategy & Contract Enforcement**

0 commit comments

Comments
 (0)