Skip to content

Commit dbb77a0

Browse files
authored
Merge pull request #3 from ray-di/form
File upload support with automatic handling
2 parents 0a058d4 + a039e66 commit dbb77a0

36 files changed

+4070
-35
lines changed

.claude/settings.local.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@
77
"Bash(./vendor/bin/phpunit:*)",
88
"Bash(./vendor/bin/phpmd:*)",
99
"Bash(vendor/bin/phpunit:*)",
10-
"Bash(XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --coverage-filter src/)"
10+
"Bash(XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --coverage-filter src/)",
11+
"Bash(find:*)",
12+
"mcp__ide__getDiagnostics",
13+
"Bash(XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --coverage-filter=src/)",
14+
"Bash(XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --coverage-filter=src/ --colors=never)",
15+
"Bash(XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html coverage-report --coverage-filter=src/)",
16+
"Bash(ls:*)",
17+
"Bash(grep:*)"
1118
],
1219
"deny": []
1320
}

README.md

Lines changed: 156 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Ray.InputQuery
22

3-
Structured input objects from HTTP.
3+
[![Continuous Integration](https://github.com/ray-di/Ray.InputQuery/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/ray-di/Ray.InputQuery/actions/workflows/continuous-integration.yml)
4+
[![Type Coverage](https://shepherd.dev/github/ray-di/Ray.InputQuery/coverage.svg)](https://shepherd.dev/github/ray-di/Ray.InputQuery)
5+
[![codecov](https://codecov.io/gh/ray-di/Ray.InputQuery/branch/main/graph/badge.svg)](https://codecov.io/gh/ray-di/Ray.InputQuery)
6+
7+
Structured input objects from HTTP with 100% test coverage.
48

59
## Overview
610

@@ -44,10 +48,28 @@ public function createTodo(TodoInput $input) {
4448
composer require ray/input-query
4549
```
4650

51+
## Demo
52+
53+
To see file upload integration in action:
54+
55+
```bash
56+
php -S localhost:8080 -t demo/
57+
```
58+
59+
Then visit [http://localhost:8080](http://localhost:8080) in your browser.
60+
4761
## Documentation
4862

4963
Comprehensive documentation including design philosophy, AI prompts for development assistance, and sample data examples can be found in the [docs/](docs/) directory.
5064

65+
### Framework Integration
66+
67+
For framework-specific integration examples, see the **[Framework Integration Guide](docs/framework_integration.md)** which covers:
68+
69+
- Laravel, Symfony, CakePHP, Yii Framework 1.x, BEAR.Sunday, and Slim Framework
70+
- Three usage patterns (Reflection, Direct Object Creation, Spread Operator)
71+
- Testing examples and best practices
72+
5173
## Usage
5274

5375
Ray.InputQuery converts flat query data into typed PHP objects automatically.
@@ -89,11 +111,11 @@ echo $user->email; // john@example.com
89111
// Method argument resolution from $_POST
90112
$method = new ReflectionMethod(UserController::class, 'register');
91113
$args = $inputQuery->getArguments($method, $_POST);
92-
$controller->register(...$args);
114+
$result = $method->invokeArgs($controller, $args);
93115

94-
// Or with PSR-7 Request
116+
// Or with PSR-7 Request
95117
$args = $inputQuery->getArguments($method, $request->getParsedBody());
96-
$controller->register(...$args);
118+
$result = $method->invokeArgs($controller, $args);
97119
```
98120

99121
### Nested Objects
@@ -185,8 +207,8 @@ $data = [
185207
]
186208
];
187209

188-
$args = $inputQuery->getArguments($method, $data);
189-
// $args[0] will be an array of UserInput objects
210+
$result = $method->invokeArgs($controller, $inputQuery->getArguments($method, $data));
211+
// Arguments automatically resolved as UserInput objects
190212
```
191213

192214
#### Simple array values (e.g., checkboxes)
@@ -286,13 +308,141 @@ All query keys are normalized to camelCase:
286308
- `user-name``userName`
287309
- `UserName``userName`
288310

311+
## File Upload Integration
312+
313+
Ray.InputQuery provides comprehensive file upload support through integration with [Koriym.FileUpload](https://github.com/koriym/Koriym.FileUpload):
314+
315+
```bash
316+
composer require koriym/file-upload
317+
```
318+
319+
### Using #[InputFile] Attribute
320+
321+
For file uploads, use the dedicated `#[InputFile]` attribute which provides validation options:
322+
323+
```php
324+
use Koriym\FileUpload\FileUpload;
325+
use Koriym\FileUpload\ErrorFileUpload;
326+
use Ray\InputQuery\Attribute\InputFile;
327+
328+
final class UserProfileInput
329+
{
330+
public function __construct(
331+
#[Input] public readonly string $name,
332+
#[Input] public readonly string $email,
333+
#[InputFile(
334+
maxSize: 5 * 1024 * 1024, // 5MB
335+
allowedTypes: ['image/jpeg', 'image/png'],
336+
allowedExtensions: ['jpg', 'jpeg', 'png']
337+
)]
338+
public readonly FileUpload|ErrorFileUpload $avatar,
339+
#[InputFile] public readonly FileUpload|ErrorFileUpload|null $banner = null,
340+
) {}
341+
}
342+
```
343+
344+
// Method usage example - Direct attribute approach
345+
346+
### Test-Friendly Design
347+
348+
File upload handling is designed to be test-friendly:
349+
350+
- **Production** - FileUpload library handles file uploads automatically
351+
- **Testing** - Direct FileUpload object injection for easy mocking
352+
353+
```php
354+
// Production usage - FileUpload library handles file uploads automatically
355+
$input = $inputQuery->create(UserProfileInput::class, $_POST);
356+
// FileUpload objects are created automatically from uploaded files
357+
358+
// Testing usage - inject mock FileUpload objects directly for easy testing
359+
$mockAvatar = FileUpload::create([
360+
'name' => 'test.jpg',
361+
'type' => 'image/jpeg',
362+
'size' => 1024,
363+
'tmp_name' => '/tmp/test',
364+
'error' => UPLOAD_ERR_OK,
365+
]);
366+
367+
$input = $inputQuery->create(UserProfileInput::class, [
368+
'name' => 'Test User',
369+
'email' => 'test@example.com',
370+
'avatar' => $mockAvatar,
371+
'banner' => null
372+
]);
373+
```
374+
375+
### Multiple File Uploads
376+
377+
Support for multiple file uploads using array types with validation:
378+
379+
```php
380+
final class GalleryInput
381+
{
382+
/**
383+
* @param list<FileUpload|ErrorFileUpload> $images
384+
*/
385+
public function __construct(
386+
#[Input] public readonly string $title,
387+
#[InputFile(
388+
maxSize: 10 * 1024 * 1024, // 10MB per file
389+
allowedTypes: ['image/*']
390+
)]
391+
public readonly array $images,
392+
) {}
393+
}
394+
395+
// Method usage example
396+
class GalleryController
397+
{
398+
public function createGallery(GalleryInput $input): void
399+
{
400+
$savedImages = [];
401+
foreach ($input->images as $image) {
402+
if ($image instanceof FileUpload) {
403+
$savedImages[] = $this->saveFile($image, 'gallery/');
404+
} elseif ($image instanceof ErrorFileUpload) {
405+
// Log error but continue with other images
406+
$this->logger->warning('Image upload failed: ' . $image->message);
407+
}
408+
}
409+
410+
$this->galleryService->create($input->title, $savedImages);
411+
}
412+
}
413+
414+
// Production usage - FileUpload library handles multiple files automatically
415+
$input = $inputQuery->create(GalleryInput::class, $_POST);
416+
// Array of FileUpload objects created automatically from uploaded files
417+
418+
// Testing usage - inject array of mock FileUpload objects for easy testing
419+
$mockImages = [
420+
FileUpload::create(['name' => 'image1.jpg', ...]),
421+
FileUpload::create(['name' => 'image2.png', ...])
422+
];
423+
424+
$input = $inputQuery->create(GalleryInput::class, [
425+
'title' => 'My Gallery',
426+
'images' => $mockImages
427+
]);
428+
```
429+
289430
## Integration
290431

291432
Ray.InputQuery is designed as a foundation library to be used by:
292433

293434
- [Ray.MediaQuery](https://github.com/ray-di/Ray.MediaQuery) - For database query integration
294435
- [BEAR.Resource](https://github.com/bearsunday/BEAR.Resource) - For REST resource integration
295436

437+
## Project Quality
438+
439+
This project maintains high quality standards:
440+
441+
- **100% Code Coverage** - Achieved through public interface tests only
442+
- **Static Analysis** - Psalm and PHPStan at maximum levels
443+
- **Test Design** - No private method tests, ensuring maintainability
444+
- **Type Safety** - Comprehensive Psalm type annotations
445+
296446
## Requirements
297447

298448
- PHP 8.1+

composer-require-checker.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
2-
"symbol-whitelist" : [],
2+
"symbol-whitelist" : [
3+
"Koriym\\FileUpload\\FileUpload",
4+
"Koriym\\FileUpload\\ErrorFileUpload"
5+
],
36
"php-core-extensions" : [
47
"Core",
58
"date",

composer.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@
1212
"php": "^8.1",
1313
"ray/di": "^2.18"
1414
},
15+
"suggest": {
16+
"koriym/file-upload": "For file upload handling integration"
17+
},
1518
"require-dev": {
1619
"bamarni/composer-bin-plugin": "^1.8",
17-
"phpunit/phpunit": "^10.5"
20+
"phpunit/phpunit": "^10.5",
21+
"koriym/file-upload": "^0.2.0"
1822
},
1923
"autoload": {
2024
"psr-4": {

0 commit comments

Comments
 (0)