PHPackages                             ray/input-query - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. ray/input-query

ActiveLibrary[HTTP &amp; Networking](/categories/http)

ray/input-query
===============

Structured input objects from HTTP

v1.0.0(10mo ago)058.0k—7.8%15MITPHPPHP ^8.1CI passing

Since Jul 3Pushed 5mo ago1 watchersCompare

[ Source](https://github.com/ray-di/Ray.InputQuery)[ Packagist](https://packagist.org/packages/ray/input-query)[ RSS](/packages/ray-input-query/feed)WikiDiscussions 1.x Synced 1mo ago

READMEChangelog (4)Dependencies (5)Versions (8)Used By (5)

Ray.InputQuery
==============

[](#rayinputquery)

[![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)[![Type Coverage](https://camo.githubusercontent.com/a990912d490758dcda70c3317f2525c16d07c30b7d4f3f2835a7a752a27f5ce8/68747470733a2f2f73686570686572642e6465762f6769746875622f7261792d64692f5261792e496e70757451756572792f636f7665726167652e737667)](https://shepherd.dev/github/ray-di/Ray.InputQuery)[![codecov](https://camo.githubusercontent.com/c0f91a77361c8e4d16cf19b6ea45dd0161ef8765e3225ee1fa4e9d0f6a9f1f29/68747470733a2f2f636f6465636f762e696f2f67682f7261792d64692f5261792e496e70757451756572792f6272616e63682f6d61696e2f67726170682f62616467652e737667)](https://codecov.io/gh/ray-di/Ray.InputQuery)

Convert HTTP query parameters into hierarchical PHP objects automatically.

Quick Example
-------------

[](#quick-example)

```
// HTTP Request: ?name=John&email=john@example.com&addressStreet=123 Main St&addressCity=Tokyo

// Automatically becomes:
final class AddressInput {
    public function __construct(
        #[Input] public readonly string $street,
        #[Input] public readonly string $city
    ) {}
}

final class UserInput {
    public function __construct(
        #[Input] public readonly string $name,
        #[Input] public readonly string $email,
        #[Input] public readonly AddressInput $address  // Nested object!
    ) {}
}

$user = $inputQuery->newInstance(UserInput::class, $_GET);
echo $user->name;            // "John"
echo $user->address->street; // "123 Main St"
```

**Key Point**: `addressStreet` and `addressCity` automatically compose the `AddressInput` object.

Overview
--------

[](#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 Features:**

- **Automatic Nesting** - Prefix-based parameters create hierarchical objects
- **Type Safety** - Leverages PHP's type system for automatic conversion
- **DI Integration** - Mix query parameters with dependency injection
- **Validation** - Type constraints ensure data integrity

Installation
------------

[](#installation)

```
composer require ray/input-query
```

### Optional: File Upload Support

[](#optional-file-upload-support)

For file upload functionality, also install:

```
composer require koriym/file-upload
```

Documentation
-------------

[](#documentation)

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

### Framework Integration

[](#framework-integration)

For framework-specific integration examples, see the **[Framework Integration Guide](docs/framework_integration.md)** which covers:

- Laravel, Symfony, CakePHP, Yii Framework 1.x, BEAR.Sunday, and Slim Framework
- Three usage patterns (Reflection, Direct Object Creation, Spread Operator)
- Testing examples and best practices

Usage
-----

[](#usage)

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

### Basic Usage

[](#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->newInstance(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);
$result = $method->invokeArgs($controller, $args);
```

### Nested Objects

[](#nested-objects)

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

```
final class AddressInput
{
    public function __construct(
        #[Input] public readonly string $street,
        #[Input] public readonly string $city,
        #[Input] public readonly string $zip
    ) {}
}

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

$user = $inputQuery->newInstance(UserInput::class, [
    'name' => 'John Doe',
    'email' => 'john@example.com',
    'addressStreet' => '123 Main St',
    'addressCity' => 'Tokyo',
    'addressZip' => '100-0001'
]);

echo $user->name;            // John Doe
echo $user->address->street; // 123 Main St
```

### Array Support

[](#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
{
    /**
     * @param list $users
     */
    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
        }
    }

    /**
     * @param ArrayObject $users
     */
    public function processUsers(
        #[Input(item: UserInput::class)] ArrayObject $users  // ArrayObject collection
    ) {
        // $users is an ArrayObject containing UserInput instances
    }
}
```

#### Query data format for arrays

[](#query-data-format-for-arrays)

Arrays should be submitted as indexed arrays. Here's how to structure HTML forms and the resulting data:

```

```

This will be received as:

```
$data = [
    'users' => [
        ['id' => '1', 'name' => 'Jingu'],
        ['id' => '2', 'name' => 'Horikawa']
    ]
];

$result = $method->invokeArgs($controller, $inputQuery->getArguments($method, $data));
// Arguments automatically resolved as UserInput objects
```

#### Simple array values (e.g., checkboxes)

[](#simple-array-values-eg-checkboxes)

For simple arrays like checkboxes or multi-select:

```

        Technology
        Business
        Lifestyle

```

This will be received as:

```
$data = [
    'hobbies' => ['music', 'sports'],      // Only checked values
    'categories' => ['tech', 'lifestyle']   // Only selected values
];

// In your controller
/**
 * @param list $hobbies
 * @param list $categories
 */
public function updatePreferences(
    #[Input] array $hobbies,      // Simple string array
    #[Input] array $categories    // Simple string array
) {
    // Direct array of strings, no object conversion needed
}
```

**Note**: For non-array parameters, use flat naming without brackets:

```

```

#### ArrayObject Inheritance Support

[](#arrayobject-inheritance-support)

Custom ArrayObject subclasses are also supported:

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

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

### Mixed with Dependency Injection

[](#mixed-with-dependency-injection)

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

```
use Ray\Di\Di\Named;

interface AddressServiceInterface
{
    public function findByZip(string $zip): Address;
}

interface TicketFactoryInterface
{
    public function create(string $eventId, string $ticketId): Ticket;
}

final class EventBookingInput
{
    public function __construct(
        #[Input] public readonly string $ticketId,        // From query - raw ID
        #[Input] public readonly string $email,           // From query
        #[Input] public readonly string $zip,             // From query
        #[Named('event_id')] private string $eventId,     // From DI
        private TicketFactoryInterface $ticketFactory,    // From DI
        private AddressServiceInterface $addressService,  // From DI
    ) {
        // Create complete Ticket object from ID (includes validation, expiry, etc.)
        $this->ticket = $this->ticketFactory->create($eventId, $ticketId);
        // Fully validated immutable ticket object created!

        if (!$this->ticket->isValid) {
            throw new InvalidTicketException(
                "Ticket {$ticketId} is invalid: {$this->ticket->getInvalidReason()}"
            );
        }

        // Get address from zip
        $this->address = $this->addressService->findByZip($zip);
    }

    public readonly Ticket $ticket;    // Complete ticket object with ID, status, etc.
    public readonly Address $address;  // Structured address object
}

// DI configuration
$injector = new Injector(new class extends AbstractModule {
    protected function configure(): void
    {
        $this->bind(TicketFactoryInterface::class)->to(TicketFactory::class);   // Can swap with mock in tests
        $this->bind(AddressServiceInterface::class)->to(AddressService::class);
        $this->bind()->annotatedWith('event_id')->toInstance('ray-event-2025');
    }
});

$inputQuery = new InputQuery($injector);

// Usage - Factory automatically creates complete objects from IDs
try {
    $booking = $inputQuery->newInstance(EventBookingInput::class, [
        'ticketId' => 'TKT-2024-001',
        'email' => 'user@example.com',
        'zip' => '100-0001'
    ]);

    // $booking->ticket is a Ticket object with ID and validation status
    echo "Ticket ID: " . $booking->ticket->id; // Only valid ticket ID

} catch (InvalidTicketException $e) {
    // Handle expired or invalid tickets
    echo "Booking failed: " . $e->getMessage();
}
```

### Key Normalization

[](#key-normalization)

All query keys are normalized to camelCase:

- `user_name` → `userName`
- `user-name` → `userName`
- `UserName` → `userName`

File Upload Integration
-----------------------

[](#file-upload-integration)

Ray.InputQuery provides comprehensive file upload support through integration with [Koriym.FileUpload](https://github.com/koriym/Koriym.FileUpload):

```
composer require koriym/file-upload
```

When using file upload features, instantiate InputQuery with FileUploadFactory:

```
use Ray\InputQuery\InputQuery;
use Ray\InputQuery\FileUploadFactory;

$inputQuery = new InputQuery($injector, new FileUploadFactory());
```

### Using #\[InputFile\] Attribute

[](#using-inputfile-attribute)

For file uploads, use the dedicated `#[InputFile]` attribute which provides validation options:

```
use Koriym\FileUpload\FileUpload;
use Koriym\FileUpload\ErrorFileUpload;
use Ray\InputQuery\Attribute\InputFile;

final class UserProfileInput
{
    public function __construct(
        #[Input] public readonly string $name,
        #[Input] public readonly string $email,
        #[InputFile(
            maxSize: 5 * 1024 * 1024,  // 5MB
            allowedTypes: ['image/jpeg', 'image/png'],
            allowedExtensions: ['jpg', 'jpeg', 'png']
        )]
        public readonly FileUpload|ErrorFileUpload $avatar,
        #[InputFile] public readonly FileUpload|ErrorFileUpload|null $banner = null,
    ) {}
}
```

// Method usage example - Direct attribute approach

### Test-Friendly Design

[](#test-friendly-design)

File upload handling is designed to be test-friendly:

- **Production** - FileUpload library handles file uploads automatically
- **Testing** - Direct FileUpload object injection for easy mocking

```
// Production usage - FileUpload library handles file uploads automatically
$input = $inputQuery->newInstance(UserProfileInput::class, $_POST);
// FileUpload objects are created automatically from uploaded files

// Testing usage - inject mock FileUpload objects directly for easy testing
$mockAvatar = FileUpload::create([
    'name' => 'test.jpg',
    'type' => 'image/jpeg',
    'size' => 1024,
    'tmp_name' => '/tmp/test',
    'error' => UPLOAD_ERR_OK,
]);

$input = $inputQuery->newInstance(UserProfileInput::class, [
    'name' => 'Test User',
    'email' => 'test@example.com',
    'avatar' => $mockAvatar,
    'banner' => null
]);
```

### Multiple File Uploads

[](#multiple-file-uploads)

Support for multiple file uploads using array types with validation:

```
final class GalleryInput
{
    /**
     * @param list $images
     */
    public function __construct(
        #[Input] public readonly string $title,
        #[InputFile(
            maxSize: 10 * 1024 * 1024,  // 10MB per file
            allowedTypes: ['image/*']
        )]
        public readonly array $images,
    ) {}
}

// Method usage example
class GalleryController
{
    public function createGallery(GalleryInput $input): void
    {
        $savedImages = [];
        foreach ($input->images as $image) {
            if ($image instanceof FileUpload) {
                $savedImages[] = $this->saveFile($image, 'gallery/');
            } elseif ($image instanceof ErrorFileUpload) {
                // Log error but continue with other images
                $this->logger->warning('Image upload failed: ' . $image->message);
            }
        }

        $this->galleryService->create($input->title, $savedImages);
    }
}

// Production usage - FileUpload library handles multiple files automatically
$input = $inputQuery->newInstance(GalleryInput::class, $_POST);
// Array of FileUpload objects created automatically from uploaded files

// Testing usage - inject array of mock FileUpload objects for easy testing
$mockImages = [
    FileUpload::create(['name' => 'image1.jpg', ...]),
    FileUpload::create(['name' => 'image2.png', ...])
];

$input = $inputQuery->newInstance(GalleryInput::class, [
    'title' => 'My Gallery',
    'images' => $mockImages
]);
```

Converting Objects to Arrays
----------------------------

[](#converting-objects-to-arrays)

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:

### Basic ToArray Usage

[](#basic-toarray-usage)

```
use Ray\InputQuery\ToArray;

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

final class OrderInput
{
    public function __construct(
        #[Input] public readonly string $id,
        #[Input] public readonly CustomerInput $customer,
        #[Input] public readonly array $items,
    ) {}
}

// Create nested input object
$orderInput = new OrderInput(
    id: 'ORD-001',
    customer: new CustomerInput(name: 'John Doe', email: 'john@example.com'),
    items: [['product' => 'laptop', 'quantity' => 1]]
);

// Convert to flat array for SQL
$toArray = new ToArray();
$params = $toArray($orderInput);

// Result:
// [
//     'id' => 'ORD-001',
//     'name' => 'John Doe',           // Flattened from customer
//     'email' => 'john@example.com',  // Flattened from customer
//     'items' => [['product' => 'laptop', 'quantity' => 1]]  // Arrays preserved
// ]
```

### SQL Param￥￥eter Binding

[](#sql-parameter-binding)

The flattened arrays work seamlessly with Aura.Sql and other SQL libraries:

```
// Using with Aura.Sql
$sql = "INSERT INTO orders (id, customer_name, customer_email) VALUES (:id, :name, :email)";
$statement = $pdo->prepare($sql);
$statement->execute($params);

// Arrays are preserved for IN clauses
$productIds = $params['productIds']; // [1, 2, 3]
$sql = "SELECT * FROM products WHERE id IN (?)";
$statement = $pdo->prepare($sql);
$statement->execute([$productIds]); // Aura.Sql handles array expansion

// Other use cases
return new JsonResponse($params);  // API responses
$this->logger->info('Order data', $params);  // Logging
```

### Property Name Conflicts

[](#property-name-conflicts)

When flattened properties have the same name, later values overwrite earlier ones:

```
final class OrderInput
{
    public function __construct(
        #[Input] public readonly string $id,           // 'ORD-001'
        #[Input] public readonly CustomerInput $customer,  // Has 'id' property: 'CUST-123'
    ) {}
}

$params = $toArray($orderInput);
// Result: ['id' => 'CUST-123']  // Customer ID overwrites order ID
```

### Key Features

[](#key-features)

- **Recursive Flattening**: Nested objects with `#[Input]` parameters are automatically flattened
- **Array Preservation**: Arrays remain intact for SQL IN clauses (Aura.Sql compatible)
- **Property Conflicts**: Later properties overwrite earlier ones
- **Public Properties Only**: Private/protected properties are ignored
- **Type Safety**: Maintains type information through transformation

### Complex Example

[](#complex-example)

```
final class AddressInput
{
    public function __construct(
        #[Input] public readonly string $street,
        #[Input] public readonly string $city,
        #[Input] public readonly string $country,
    ) {}
}

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

final class OrderInput
{
    public function __construct(
        #[Input] public readonly string $orderId,
        #[Input] public readonly CustomerInput $customer,
        #[Input] public readonly AddressInput $shipping,
        #[Input] public readonly array $productIds,
    ) {}
}

$order = new OrderInput(
    orderId: 'ORD-001',
    customer: new CustomerInput(
        name: 'John Doe',
        email: 'john@example.com',
        address: new AddressInput(street: '123 Main St', city: 'Tokyo', country: 'Japan')
    ),
    shipping: new AddressInput(street: '456 Oak Ave', city: 'Osaka', country: 'Japan'),
    productIds: ['PROD-1', 'PROD-2', 'PROD-3']
);

$params = $toArray($order);
// Result:
// [
//     'orderId' => 'ORD-001',
//     'name' => 'John Doe',
//     'email' => 'john@example.com',
//     'street' => '456 Oak Ave',      // Shipping address overwrites customer address
//     'city' => 'Osaka',             // Shipping address overwrites customer address
//     'country' => 'Japan',          // Same value, so no visible conflict
//     'productIds' => ['PROD-1', 'PROD-2', 'PROD-3']  // Array preserved
// ]

// Use the flattened data
$orderId = $params['orderId'];
$customerName = $params['name'];
$shippingAddress = "{$params['street']}, {$params['city']}, {$params['country']}";
$productIds = $params['productIds']; // Array preserved
```

Demo
----

[](#demo)

### Web Demo

[](#web-demo)

To see file upload integration in action:

```
php -S localhost:8080 -t demo/
```

Then visit  in your browser.

### Console Demos

[](#console-demos)

Run various examples from the command line:

```
# Basic examples with nested objects and DI
php demo/run.php

# Array processing demo
php demo/ArrayDemo.php

# CSV file processing with batch operations
php demo/csv/run.php
```

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance63

Regular maintenance activity

Popularity32

Limited adoption so far

Community16

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 96.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

5

Last Release

306d ago

Major Versions

v0.2.0 → 1.x-dev2025-07-16

### Community

Maintainers

![](https://www.gravatar.com/avatar/db4fc75ffc631168d0d7143b6f2c24b1534dfb921212bd851c026c5cbbb1344d?d=identicon)[koriym](/maintainers/koriym)

---

Top Contributors

[![koriym](https://avatars.githubusercontent.com/u/529021?v=4)](https://github.com/koriym "koriym (99 commits)")[![apple-x-co](https://avatars.githubusercontent.com/u/8497012?v=4)](https://github.com/apple-x-co "apple-x-co (4 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/ray-input-query/health.svg)

```
[![Health](https://phpackages.com/badges/ray-input-query/health.svg)](https://phpackages.com/packages/ray-input-query)
```

###  Alternatives

[friendsofsymfony/rest-bundle

This Bundle provides various tools to rapidly develop RESTful API's with Symfony

2.8k73.3M319](/packages/friendsofsymfony-rest-bundle)[php-http/discovery

Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations

1.3k309.5M1.2k](/packages/php-http-discovery)[pusher/pusher-php-server

Library for interacting with the Pusher REST API

1.5k94.8M293](/packages/pusher-pusher-php-server)[react/http

Event-driven, streaming HTTP client and server implementation for ReactPHP

78026.4M414](/packages/react-http)[php-http/curl-client

PSR-18 and HTTPlug Async client with cURL

48347.0M384](/packages/php-http-curl-client)[smi2/phpclickhouse

PHP ClickHouse Client

84310.1M71](/packages/smi2-phpclickhouse)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
