|
4 | 4 | [](https://shepherd.dev/github/ray-di/Ray.InputQuery) |
5 | 5 | [](https://codecov.io/gh/ray-di/Ray.InputQuery) |
6 | 6 |
|
7 | | -Structured input objects from HTTP with 100% test coverage. |
| 7 | +Convert HTTP query parameters into hierarchical PHP objects automatically. |
8 | 8 |
|
9 | | -## Overview |
10 | | - |
11 | | -Ray.InputQuery transforms flat HTTP data into structured PHP objects through explicit type declarations. Using the `#[Input]` attribute, you declare which parameters come from query data, while other parameters are resolved via dependency injection. |
| 9 | +## Quick Example |
12 | 10 |
|
13 | | -**Core Mechanism:** |
14 | | -- **Attribute-Based Control** - `#[Input]` explicitly marks query-sourced parameters |
15 | | -- **Prefix-Based Nesting** - `assigneeId`, `assigneeName` fields automatically compose `UserInput` objects |
16 | | -- **Type-Safe Conversion** - Leverages PHP's type system for automatic scalar conversion |
17 | | -- **DI Integration** - Parameters without `#[Input]` are resolved from dependency injection |
18 | | - |
19 | | -**The Problem:** |
20 | 11 | ```php |
21 | | -// Manual parameter extraction and object construction |
22 | | -$data = $request->getParsedBody(); // or $_POST |
23 | | -$title = $data['title'] ?? ''; |
24 | | -$assigneeId = $data['assigneeId'] ?? ''; |
25 | | -$assigneeName = $data['assigneeName'] ?? ''; |
26 | | -$assigneeEmail = $data['assigneeEmail'] ?? ''; |
27 | | -``` |
| 12 | +// HTTP Request: ?name=John&email=john@example.com&addressStreet=123 Main St&addressCity=Tokyo |
28 | 13 |
|
29 | | -**Ray.InputQuery Solution:** |
30 | | -```php |
31 | | -// Declarative structure definition |
32 | | -final class TodoInput { |
| 14 | +// Automatically becomes: |
| 15 | +final class AddressInput { |
33 | 16 | public function __construct( |
34 | | - #[Input] public readonly string $title, |
35 | | - #[Input] public readonly UserInput $assignee, // Auto-composed from assigneeId, assigneeName, assigneeEmail |
36 | | - private LoggerInterface $logger // From DI container |
| 17 | + #[Input] public readonly string $street, |
| 18 | + #[Input] public readonly string $city |
37 | 19 | ) {} |
38 | 20 | } |
39 | 21 |
|
40 | | -public function createTodo(TodoInput $input) { |
41 | | - // $input automatically structured from request data |
| 22 | +final class UserInput { |
| 23 | + public function __construct( |
| 24 | + #[Input] public readonly string $name, |
| 25 | + #[Input] public readonly string $email, |
| 26 | + #[Input] public readonly AddressInput $address // Nested object! |
| 27 | + ) {} |
42 | 28 | } |
43 | | -``` |
44 | 29 |
|
45 | | -## Installation |
46 | | - |
47 | | -```bash |
48 | | -composer require ray/input-query |
| 30 | +$user = $inputQuery->newInstance(UserInput::class, $_GET); |
| 31 | +echo $user->name; // "John" |
| 32 | +echo $user->address->street; // "123 Main St" |
49 | 33 | ``` |
50 | 34 |
|
51 | | -### Optional: File Upload Support |
| 35 | +**Key Point**: `addressStreet` and `addressCity` automatically compose the `AddressInput` object. |
52 | 36 |
|
53 | | -For file upload functionality, also install: |
| 37 | +## Overview |
54 | 38 |
|
55 | | -```bash |
56 | | -composer require koriym/file-upload |
57 | | -``` |
| 39 | +Ray.InputQuery transforms flat HTTP data into structured PHP objects through explicit type declarations. Using the `#[Input]` attribute, you declare which parameters come from query data, while other parameters are resolved via dependency injection. |
58 | 40 |
|
59 | | -## Demo |
| 41 | +**Core Features:** |
| 42 | +- **Automatic Nesting** - Prefix-based parameters create hierarchical objects |
| 43 | +- **Type Safety** - Leverages PHP's type system for automatic conversion |
| 44 | +- **DI Integration** - Mix query parameters with dependency injection |
| 45 | +- **Validation** - Type constraints ensure data integrity |
60 | 46 |
|
61 | | -### Web Demo |
62 | | - |
63 | | -To see file upload integration in action: |
| 47 | +## Installation |
64 | 48 |
|
65 | 49 | ```bash |
66 | | -php -S localhost:8080 -t demo/ |
| 50 | +composer require ray/input-query |
67 | 51 | ``` |
68 | 52 |
|
69 | | -Then visit [http://localhost:8080](http://localhost:8080) in your browser. |
70 | | - |
71 | | -### Console Demos |
| 53 | +### Optional: File Upload Support |
72 | 54 |
|
73 | | -Run various examples from the command line: |
| 55 | +For file upload functionality, also install: |
74 | 56 |
|
75 | 57 | ```bash |
76 | | -# Basic examples with nested objects and DI |
77 | | -php demo/run.php |
78 | | - |
79 | | -# Array processing demo |
80 | | -php demo/ArrayDemo.php |
81 | | - |
82 | | -# CSV file processing with batch operations |
83 | | -php demo/csv/run.php |
| 58 | +composer require koriym/file-upload |
84 | 59 | ``` |
85 | 60 |
|
86 | 61 | ## Documentation |
@@ -136,10 +111,6 @@ echo $user->email; // john@example.com |
136 | 111 | // Method argument resolution from $_POST |
137 | 112 | $method = new ReflectionMethod(UserController::class, 'register'); |
138 | 113 | $args = $inputQuery->getArguments($method, $_POST); |
139 | | -$result = $method->invokeArgs($controller, $args); |
140 | | - |
141 | | - // Or with PSR-7 Request |
142 | | -$args = $inputQuery->getArguments($method, $request->getParsedBody()); |
143 | 114 | $result = $method->invokeArgs($controller, $args); |
144 | 115 | ``` |
145 | 116 |
|
@@ -528,3 +499,183 @@ $input = $inputQuery->newInstance(GalleryInput::class, [ |
528 | 499 | 'images' => $mockImages |
529 | 500 | ]); |
530 | 501 | ``` |
| 502 | + |
| 503 | +## Converting Objects to Arrays |
| 504 | + |
| 505 | +Ray.InputQuery provides the `ToArray` functionality to convert objects with `#[Input]` parameters into flat associative arrays, primarily for SQL parameter binding with libraries like Aura.Sql: |
| 506 | + |
| 507 | +### Basic ToArray Usage |
| 508 | + |
| 509 | +```php |
| 510 | +use Ray\InputQuery\ToArray; |
| 511 | + |
| 512 | +final class CustomerInput |
| 513 | +{ |
| 514 | + public function __construct( |
| 515 | + #[Input] public readonly string $name, |
| 516 | + #[Input] public readonly string $email, |
| 517 | + ) {} |
| 518 | +} |
| 519 | + |
| 520 | +final class OrderInput |
| 521 | +{ |
| 522 | + public function __construct( |
| 523 | + #[Input] public readonly string $id, |
| 524 | + #[Input] public readonly CustomerInput $customer, |
| 525 | + #[Input] public readonly array $items, |
| 526 | + ) {} |
| 527 | +} |
| 528 | + |
| 529 | +// Create nested input object |
| 530 | +$orderInput = new OrderInput( |
| 531 | + id: 'ORD-001', |
| 532 | + customer: new CustomerInput(name: 'John Doe', email: 'john@example.com'), |
| 533 | + items: [['product' => 'laptop', 'quantity' => 1]] |
| 534 | +); |
| 535 | + |
| 536 | +// Convert to flat array for SQL |
| 537 | +$toArray = new ToArray(); |
| 538 | +$params = $toArray($orderInput); |
| 539 | + |
| 540 | +// Result: |
| 541 | +// [ |
| 542 | +// 'id' => 'ORD-001', |
| 543 | +// 'name' => 'John Doe', // Flattened from customer |
| 544 | +// 'email' => 'john@example.com', // Flattened from customer |
| 545 | +// 'items' => [['product' => 'laptop', 'quantity' => 1]] // Arrays preserved |
| 546 | +// ] |
| 547 | +``` |
| 548 | + |
| 549 | +### SQL Param¥¥eter Binding |
| 550 | + |
| 551 | +The flattened arrays work seamlessly with Aura.Sql and other SQL libraries: |
| 552 | + |
| 553 | +```php |
| 554 | +// Using with Aura.Sql |
| 555 | +$sql = "INSERT INTO orders (id, customer_name, customer_email) VALUES (:id, :name, :email)"; |
| 556 | +$statement = $pdo->prepare($sql); |
| 557 | +$statement->execute($params); |
| 558 | + |
| 559 | +// Arrays are preserved for IN clauses |
| 560 | +$productIds = $params['productIds']; // [1, 2, 3] |
| 561 | +$sql = "SELECT * FROM products WHERE id IN (?)"; |
| 562 | +$statement = $pdo->prepare($sql); |
| 563 | +$statement->execute([$productIds]); // Aura.Sql handles array expansion |
| 564 | + |
| 565 | +// Other use cases |
| 566 | +return new JsonResponse($params); // API responses |
| 567 | +$this->logger->info('Order data', $params); // Logging |
| 568 | +``` |
| 569 | + |
| 570 | +### Property Name Conflicts |
| 571 | + |
| 572 | +When flattened properties have the same name, later values overwrite earlier ones: |
| 573 | + |
| 574 | +```php |
| 575 | +final class OrderInput |
| 576 | +{ |
| 577 | + public function __construct( |
| 578 | + #[Input] public readonly string $id, // 'ORD-001' |
| 579 | + #[Input] public readonly CustomerInput $customer, // Has 'id' property: 'CUST-123' |
| 580 | + ) {} |
| 581 | +} |
| 582 | + |
| 583 | +$params = $toArray($orderInput); |
| 584 | +// Result: ['id' => 'CUST-123'] // Customer ID overwrites order ID |
| 585 | +``` |
| 586 | + |
| 587 | +### Key Features |
| 588 | + |
| 589 | +- **Recursive Flattening**: Nested objects with `#[Input]` parameters are automatically flattened |
| 590 | +- **Array Preservation**: Arrays remain intact for SQL IN clauses (Aura.Sql compatible) |
| 591 | +- **Property Conflicts**: Later properties overwrite earlier ones |
| 592 | +- **Public Properties Only**: Private/protected properties are ignored |
| 593 | +- **Type Safety**: Maintains type information through transformation |
| 594 | + |
| 595 | +### Complex Example |
| 596 | + |
| 597 | +```php |
| 598 | +final class AddressInput |
| 599 | +{ |
| 600 | + public function __construct( |
| 601 | + #[Input] public readonly string $street, |
| 602 | + #[Input] public readonly string $city, |
| 603 | + #[Input] public readonly string $country, |
| 604 | + ) {} |
| 605 | +} |
| 606 | + |
| 607 | +final class CustomerInput |
| 608 | +{ |
| 609 | + public function __construct( |
| 610 | + #[Input] public readonly string $name, |
| 611 | + #[Input] public readonly string $email, |
| 612 | + #[Input] public readonly AddressInput $address, |
| 613 | + ) {} |
| 614 | +} |
| 615 | + |
| 616 | +final class OrderInput |
| 617 | +{ |
| 618 | + public function __construct( |
| 619 | + #[Input] public readonly string $orderId, |
| 620 | + #[Input] public readonly CustomerInput $customer, |
| 621 | + #[Input] public readonly AddressInput $shipping, |
| 622 | + #[Input] public readonly array $productIds, |
| 623 | + ) {} |
| 624 | +} |
| 625 | + |
| 626 | +$order = new OrderInput( |
| 627 | + orderId: 'ORD-001', |
| 628 | + customer: new CustomerInput( |
| 629 | + name: 'John Doe', |
| 630 | + email: 'john@example.com', |
| 631 | + address: new AddressInput(street: '123 Main St', city: 'Tokyo', country: 'Japan') |
| 632 | + ), |
| 633 | + shipping: new AddressInput(street: '456 Oak Ave', city: 'Osaka', country: 'Japan'), |
| 634 | + productIds: ['PROD-1', 'PROD-2', 'PROD-3'] |
| 635 | +); |
| 636 | + |
| 637 | +$params = $toArray($order); |
| 638 | +// Result: |
| 639 | +// [ |
| 640 | +// 'orderId' => 'ORD-001', |
| 641 | +// 'name' => 'John Doe', |
| 642 | +// 'email' => 'john@example.com', |
| 643 | +// 'street' => '456 Oak Ave', // Shipping address overwrites customer address |
| 644 | +// 'city' => 'Osaka', // Shipping address overwrites customer address |
| 645 | +// 'country' => 'Japan', // Same value, so no visible conflict |
| 646 | +// 'productIds' => ['PROD-1', 'PROD-2', 'PROD-3'] // Array preserved |
| 647 | +// ] |
| 648 | + |
| 649 | +// Use the flattened data |
| 650 | +$orderId = $params['orderId']; |
| 651 | +$customerName = $params['name']; |
| 652 | +$shippingAddress = "{$params['street']}, {$params['city']}, {$params['country']}"; |
| 653 | +$productIds = $params['productIds']; // Array preserved |
| 654 | +``` |
| 655 | + |
| 656 | +## Demo |
| 657 | + |
| 658 | +### Web Demo |
| 659 | + |
| 660 | +To see file upload integration in action: |
| 661 | + |
| 662 | +```bash |
| 663 | +php -S localhost:8080 -t demo/ |
| 664 | +``` |
| 665 | + |
| 666 | +Then visit [http://localhost:8080](http://localhost:8080) in your browser. |
| 667 | + |
| 668 | +### Console Demos |
| 669 | + |
| 670 | +Run various examples from the command line: |
| 671 | + |
| 672 | +```bash |
| 673 | +# Basic examples with nested objects and DI |
| 674 | +php demo/run.php |
| 675 | + |
| 676 | +# Array processing demo |
| 677 | +php demo/ArrayDemo.php |
| 678 | + |
| 679 | +# CSV file processing with batch operations |
| 680 | +php demo/csv/run.php |
| 681 | +``` |
0 commit comments