Skip to content

Commit 5c4f56b

Browse files
koriymclaude
andcommitted
docs: Add file upload integration design documentation
- Document technical design decisions for file upload support - Explain environment-agnostic approach and test-friendly patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent fd463af commit 5c4f56b

File tree

1 file changed

+362
-0
lines changed

1 file changed

+362
-0
lines changed
Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
# File Upload Integration Design
2+
3+
## Overview
4+
5+
This document outlines the design for integrating Koriym.FileUpload with Ray.InputQuery to provide seamless file upload handling alongside form data processing. The goal is to enable declarative file upload handling using the same `#[Input]` attribute pattern.
6+
7+
## Problem Statement
8+
9+
Currently, Ray.InputQuery handles flat key-value data transformation but does not process `$_FILES` directly. File uploads require separate handling, breaking the unified declarative approach that Ray.InputQuery provides for other form data.
10+
11+
## Proposed Solution
12+
13+
### Vision
14+
15+
```php
16+
use Koriym\FileUpload\FileUpload;
17+
use Ray\InputQuery\Attribute\Input;
18+
19+
final class UserProfileInput
20+
{
21+
public function __construct(
22+
#[Input] public readonly string $name,
23+
#[Input] public readonly string $email,
24+
#[Input(fileOptions: [
25+
'maxSize' => 5 * 1024 * 1024,
26+
'allowedTypes' => ['image/jpeg', 'image/png']
27+
])] public readonly FileUpload $avatar,
28+
#[Input] public readonly ?FileUpload $banner = null // Optional file
29+
) {}
30+
}
31+
32+
// Usage
33+
$inputQuery = new InputQuery($injector, $_FILES); // Pass $_FILES
34+
$input = $inputQuery->create(UserProfileInput::class, $_POST);
35+
```
36+
37+
### HTML Form Structure
38+
39+
```html
40+
<form method="post" enctype="multipart/form-data">
41+
<input name="name" value="Jingu">
42+
<input name="email" value="jingu@example.com">
43+
<input name="avatar" type="file" accept="image/*" required>
44+
<input name="banner" type="file" accept="image/*">
45+
<button type="submit">Submit</button>
46+
</form>
47+
```
48+
49+
## Implementation Design
50+
51+
### 1. Enhanced Input Attribute
52+
53+
```php
54+
#[Attribute(Attribute::TARGET_PARAMETER)]
55+
final class Input
56+
{
57+
public function __construct(
58+
public readonly string|null $item = null,
59+
public readonly array|null $fileOptions = null,
60+
) {}
61+
}
62+
```
63+
64+
### 2. InputQuery Constructor Enhancement
65+
66+
```php
67+
final class InputQuery implements InputQueryInterface
68+
{
69+
public function __construct(
70+
private InjectorInterface $injector,
71+
private array $files = [], // $_FILES data
72+
) {}
73+
}
74+
```
75+
76+
### 3. File Upload Detection Logic
77+
78+
```php
79+
private function resolveObjectType(
80+
ReflectionParameter $param,
81+
array $query,
82+
array $inputAttributes,
83+
ReflectionNamedType $type
84+
): mixed {
85+
$paramName = $param->getName();
86+
$className = $type->getName();
87+
88+
// Check for FileUpload type
89+
if ($className === FileUpload::class || is_subclass_of($className, FileUpload::class)) {
90+
return $this->resolveFileUpload($param, $inputAttributes);
91+
}
92+
93+
// Existing object resolution logic...
94+
}
95+
96+
private function resolveFileUpload(
97+
ReflectionParameter $param,
98+
array $inputAttributes
99+
): FileUpload|ErrorFileUpload {
100+
$paramName = $param->getName();
101+
102+
if (!array_key_exists($paramName, $this->files)) {
103+
if ($param->allowsNull() || $param->isDefaultValueAvailable()) {
104+
return $param->getDefaultValue();
105+
}
106+
throw new InvalidArgumentException("Required file parameter '{$paramName}' is missing");
107+
}
108+
109+
$fileData = $this->files[$paramName];
110+
$inputAttribute = $inputAttributes[0]->newInstance();
111+
$fileOptions = $inputAttribute->fileOptions ?? [];
112+
113+
return FileUpload::create($fileData, $fileOptions);
114+
}
115+
```
116+
117+
### 4. Array File Upload Support
118+
119+
```php
120+
/**
121+
* @param list<FileUpload> $images
122+
*/
123+
public function uploadGallery(
124+
#[Input(item: FileUpload::class, fileOptions: [
125+
'maxSize' => 2 * 1024 * 1024,
126+
'allowedTypes' => ['image/jpeg', 'image/png']
127+
])] array $images
128+
) {
129+
foreach ($images as $image) {
130+
$image->move('./uploads/' . $image->name);
131+
}
132+
}
133+
```
134+
135+
HTML:
136+
```html
137+
<form method="post" enctype="multipart/form-data">
138+
<input name="images[]" type="file" multiple accept="image/*">
139+
<button type="submit">Upload Gallery</button>
140+
</form>
141+
```
142+
143+
## Implementation Considerations
144+
145+
### 1. Dependency Management
146+
147+
- Add `koriym/file-upload` as an optional dependency
148+
- Use interface detection to enable FileUpload features only when the library is available
149+
- Provide graceful degradation when FileUpload is not installed
150+
151+
```php
152+
private function isFileUploadAvailable(): bool
153+
{
154+
return class_exists(FileUpload::class);
155+
}
156+
```
157+
158+
### 2. Error Handling
159+
160+
```php
161+
// Return ErrorFileUpload for validation failures
162+
if ($upload instanceof ErrorFileUpload) {
163+
if ($param->allowsNull()) {
164+
return null;
165+
}
166+
throw new InvalidArgumentException("File upload error: " . $upload->message);
167+
}
168+
```
169+
170+
### 3. Testing Strategy
171+
172+
```php
173+
// In tests, use FileUpload::fromFile() for easy testing
174+
$upload = FileUpload::fromFile(__DIR__ . '/fixtures/test-image.jpg');
175+
$input = $inputQuery->create(UserProfileInput::class, [
176+
'name' => 'Test User',
177+
'email' => 'test@example.com'
178+
], ['avatar' => $upload->toArray()]);
179+
```
180+
181+
### 4. Backward Compatibility
182+
183+
- Maintain existing constructor signature by making `$files` parameter optional
184+
- Existing code without file uploads continues to work unchanged
185+
- New file upload features are opt-in
186+
187+
## Benefits
188+
189+
### 1. Unified Declarative Approach
190+
- File uploads use the same `#[Input]` attribute pattern
191+
- Consistent validation and error handling
192+
- Type-safe file handling
193+
194+
### 2. Enhanced Developer Experience
195+
- No need to manually process `$_FILES`
196+
- Built-in validation through fileOptions
197+
- Seamless integration with existing form processing
198+
199+
### 3. Framework Integration
200+
- Works naturally with BEAR.Resource
201+
- Maintains Ray.InputQuery's design philosophy
202+
- Leverages existing DI and attribute infrastructure
203+
204+
## Migration Path
205+
206+
### Phase 1: Core Integration
207+
1. Add optional FileUpload dependency
208+
2. Enhance Input attribute with fileOptions
209+
3. Implement basic FileUpload resolution
210+
4. Add comprehensive tests
211+
212+
### Phase 2: Advanced Features
213+
1. Array file upload support
214+
2. Custom FileUpload subclass support
215+
3. Enhanced error handling and validation
216+
4. Performance optimizations
217+
218+
### Phase 3: Documentation and Examples
219+
1. Update README with file upload examples
220+
2. Create demo applications
221+
3. Add to sample data generators
222+
4. Integration guides for BEAR.Resource
223+
224+
## Technical Challenges
225+
226+
### 1. Multiple Data Sources
227+
- Handling both `$_POST` and `$_FILES` data
228+
- Maintaining separation of concerns
229+
- Consistent parameter resolution
230+
231+
### 2. Validation Timing
232+
- FileUpload validation occurs during object creation
233+
- May need to delay validation for better error reporting
234+
- Integration with form validation frameworks
235+
236+
### 3. Memory Management
237+
- Large file uploads and memory usage
238+
- Streaming capabilities for large files
239+
- Cleanup of temporary files
240+
241+
## Alternative Approaches Considered
242+
243+
### 1. Separate FileInputQuery Class
244+
- **Pros**: Clear separation, no API changes
245+
- **Cons**: Breaks unified approach, requires separate handling
246+
247+
### 2. FileUpload as Regular Objects
248+
- **Pros**: Simple implementation
249+
- **Cons**: Loses FileUpload's validation and security features
250+
251+
### 3. Custom File Attribute
252+
- **Pros**: Dedicated file handling
253+
- **Cons**: Inconsistent with existing Input attribute pattern
254+
255+
## Koriym.FileUpload Quality Assessment
256+
257+
### Code Quality Evaluation: **A+**
258+
259+
After thorough analysis of the Koriym.FileUpload source code, the library demonstrates exceptional quality and architectural alignment with Ray.InputQuery.
260+
261+
#### Strengths
262+
263+
**1. Type Safety Excellence**
264+
- Comprehensive Psalm type annotations (`@psalm-type UploadedFile`, `@psalm-immutable`)
265+
- Strict type checking with `declare(strict_types=1)`
266+
- Static analysis optimized codebase
267+
268+
**2. Immutable Design Philosophy**
269+
- `@psalm-immutable` annotations ensure side-effect-free operations
270+
- Predictable behavior and thread safety
271+
- Aligns perfectly with Ray.InputQuery's philosophy
272+
273+
**3. Robust Factory Pattern**
274+
- `FileUpload::create()` provides controlled object instantiation
275+
- Returns `ErrorFileUpload` on validation failure instead of throwing exceptions
276+
- Consistent error handling through return values
277+
278+
**4. Environment Adaptability**
279+
- Web environment: `move_uploaded_file()` for security
280+
- CLI environment: `rename()` for testing compatibility
281+
- `fromFile()` method specifically designed for test scenarios
282+
283+
**5. Comprehensive Error Handling**
284+
- Proper handling of PHP's standard upload error codes
285+
- Human-readable error messages
286+
- Unified error representation through `ErrorFileUpload`
287+
288+
#### Integration Compatibility
289+
290+
**1. Architectural Harmony**
291+
- ✅ Shared immutable design philosophy
292+
- ✅ Type safety as core principle
293+
- ✅ Error handling through return values vs exceptions
294+
- ✅ Factory pattern usage
295+
296+
**2. Seamless Integration Points**
297+
```php
298+
// Type detection in Ray.InputQuery
299+
if ($className === FileUpload::class) {
300+
return FileUpload::create($this->files[$paramName], $fileOptions);
301+
}
302+
303+
// Unified error handling
304+
if ($upload instanceof ErrorFileUpload) {
305+
// Handle validation errors consistently
306+
}
307+
```
308+
309+
**3. Testing Integration**
310+
```php
311+
// Natural testing approach
312+
$testUpload = FileUpload::fromFile(__DIR__ . '/fixtures/test.jpg');
313+
$input = $inputQuery->create(UserInput::class, $_POST, [
314+
'avatar' => $testUpload->toArray()
315+
]);
316+
```
317+
318+
#### Integration Benefits
319+
320+
**1. Consistent Developer Experience**
321+
- Same quality standards as Ray.InputQuery
322+
- Identical design principles (immutable, type-safe)
323+
- Matching error handling patterns
324+
325+
**2. Implementation Simplicity**
326+
- Natural integration with existing code patterns
327+
- No complex transformation layers required
328+
- Concise test code
329+
330+
**3. Security by Design**
331+
- Proper `move_uploaded_file()` usage
332+
- Built-in validation capabilities
333+
- Type safety prevents unexpected behaviors
334+
335+
### Technical Recommendations
336+
337+
**Implementation Priority**
338+
```php
339+
// High Priority: Basic integration
340+
#[Input] public readonly FileUpload $avatar
341+
342+
// Medium Priority: Validation options
343+
#[Input(fileOptions: ['maxSize' => 1024*1024])]
344+
public readonly FileUpload $avatar
345+
346+
// Lower Priority: Array support
347+
#[Input(item: FileUpload::class)]
348+
public readonly array $images
349+
```
350+
351+
**Potential Enhancements**
352+
- Helper methods for array upload processing
353+
- Enhanced validation option integration with Ray.InputQuery
354+
- Custom FileUpload subclass support optimization
355+
356+
## Conclusion
357+
358+
The proposed integration maintains Ray.InputQuery's declarative philosophy while adding powerful file upload capabilities. By leveraging Koriym.FileUpload's type-safe approach and Ray.InputQuery's attribute-based design, developers can handle complex forms with both regular data and file uploads using a unified, declarative interface.
359+
360+
**Quality Assessment Conclusion**: Koriym.FileUpload exceeds expectations with exceptional code quality, making it an ideal integration candidate. The architectural alignment between both libraries ensures that the proposed integration will deliver a groundbreaking developer experience for PHP file upload processing.
361+
362+
This design preserves backward compatibility while opening up new possibilities for web application development in the Ray ecosystem.

0 commit comments

Comments
 (0)