|
| 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