Skip to content

Latest commit

 

History

History
265 lines (201 loc) · 6.67 KB

File metadata and controls

265 lines (201 loc) · 6.67 KB

Ray.InputQuery

Structured input objects from HTTP.

Overview

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.

Core Mechanism:

  • Attribute-Based Control - #[Input] explicitly marks query-sourced parameters
  • Prefix-Based Nesting - assigneeId, assigneeName fields automatically compose UserInput objects
  • Type-Safe Conversion - Leverages PHP's type system for automatic scalar conversion
  • DI Integration - Parameters without #[Input] are resolved from dependency injection

The Problem:

// Manual parameter extraction and object construction
$data = $request->getParsedBody(); // or $_POST
$title = $data['title'] ?? '';
$assigneeId = $data['assigneeId'] ?? '';
$assigneeName = $data['assigneeName'] ?? '';
$assigneeEmail = $data['assigneeEmail'] ?? '';


**Ray.InputQuery Solution:**
```php
// Declarative structure definition
final class TodoInput {
    public function __construct(
        #[Input] public readonly string $title,
        #[Input] public readonly UserInput $assignee,  // Auto-composed from assigneeId, assigneeName, assigneeEmail
        private LoggerInterface $logger  // From DI container
    ) {}
}

public function createTodo(TodoInput $input) {
    // $input automatically structured from request data
}

Installation

composer require ray/input-query

Documentation

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

Usage

Ray.InputQuery converts flat query data into typed PHP objects automatically.

Basic Usage

Define your input class with the #[Input] attribute on parameters that come from query data:

use Ray\InputQuery\Attribute\Input;

final class UserInput
{
    public function __construct(
        #[Input] public readonly string $name,
        #[Input] public readonly string $email
    ) {}
}

Create input objects from query data:

use Ray\InputQuery\InputQuery;
use Ray\Di\Injector;

$injector = new Injector();
$inputQuery = new InputQuery($injector);

// Create object directly from array
$user = $inputQuery->create(UserInput::class, [
    'name' => 'John Doe',
    'email' => 'john@example.com'
]);

echo $user->name;  // John Doe
echo $user->email; // john@example.com

// Method argument resolution from $_POST
$method = new ReflectionMethod(UserController::class, 'register');
$args = $inputQuery->getArguments($method, $_POST);
$controller->register(...$args);

// Or with PSR-7 Request
$args = $inputQuery->getArguments($method, $request->getParsedBody());
$controller->register(...$args);

Nested Objects

Ray.InputQuery automatically creates nested objects from flat query data:

final class TodoInput
{
    public function __construct(
        #[Input] public readonly string $title,
        #[Input] public readonly UserInput $assignee  // Nested input
    ) {}
}

$todo = $inputQuery->create(TodoInput::class, [
    'title' => 'Buy milk',
    'assigneeId' => '123',
    'assigneeName' => 'John',
    'assigneeEmail' => 'john@example.com'
]);

echo $todo->title;           // Buy milk
echo $todo->assignee->name;  // John

Array Support

Ray.InputQuery supports arrays and ArrayObject collections with the item parameter:

use Ray\InputQuery\Attribute\Input;

final class UserInput
{
    public function __construct(
        #[Input] public readonly string $id,
        #[Input] public readonly string $name
    ) {}
}

final class UserListController
{
    public function updateUsers(
        #[Input(item: UserInput::class)] array $users  // Array of UserInput objects
    ) {
        foreach ($users as $user) {
            echo $user->name; // Each element is a UserInput instance
        }
    }
    
    public function processUsers(
        #[Input(item: UserInput::class)] ArrayObject $users  // ArrayObject collection
    ) {
        // $users is an ArrayObject containing UserInput instances
    }
}

Query data format for arrays:

$data = [
    'users' => [
        ['id' => '1', 'name' => 'John'],
        ['id' => '2', 'name' => 'Jane'],
        ['id' => '3', 'name' => 'Bob']
    ]
];

$args = $inputQuery->getArguments($method, $data);
// $args[0] will be an array of UserInput objects

ArrayObject Inheritance Support:

Custom ArrayObject subclasses are also supported:

final class UserCollection extends ArrayObject
{
    public function getFirst(): ?UserInput
    {
        return $this[0] ?? null;
    }
}

public function handleUsers(
    #[Input(item: UserInput::class)] UserCollection $users
) {
    $firstUser = $users->getFirst(); // Custom method available
}

Mixed with Dependency Injection

Parameters without the #[Input] attribute are resolved via dependency injection:

use Ray\Di\Di\Named;

final class OrderInput
{
    public function __construct(
        #[Input] public readonly string $orderId,         // From query
        #[Input] public readonly CustomerInput $customer,  // From query
        #[Named('tax.rate')] private float $taxRate,      // From DI
        private LoggerInterface $logger                    // From DI
    ) {}
}

Key Normalization

All query keys are normalized to camelCase:

  • user_nameuserName
  • user-nameuserName
  • UserNameuserName

Input Format Requirements

Ray.InputQuery expects flat key-value pairs as input. Nested array structures are not supported:

// ✅ Correct - Flat structure
$data = [
    'customerName' => 'John Doe',
    'customerEmail' => 'john@example.com',
    'shippingCity' => 'Tokyo'
];

// ❌ Wrong - Nested arrays (e.g., from customer[name] form fields)
$data = [
    'customer' => [
        'name' => 'John Doe',
        'email' => 'john@example.com'
    ]
];

Why this restriction? When nested objects are flattened for database operations, all property names must be globally unique to avoid conflicts. This design ensures predictable parameter binding and prevents naming collisions. For HTML forms, use flat naming:

<!-- ✅ Correct -->
<input name="customerName">
<input name="customerEmail">

<!-- ❌ Avoid -->
<input name="customer[name]">
<input name="customer[email]">

Integration

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

Requirements

  • PHP 8.1+
  • ray/di ^2.0

License

MIT