When was the last time you thought deeply about how your application handles input? If you're like most developers, you probably treat input as a necessary evil - something to validate, sanitize, and quickly transform into your domain objects.
But what if we've been thinking about input all wrong?
Let's look at a typical web application endpoint:
public function createArticle(Request $request)
{
$title = $request->input('title');
$content = $request->input('content');
$authorName = $request->input('author_name');
$authorEmail = $request->input('author_email');
$tags = $request->input('tags', []);
// Manual object construction
$author = new Author($authorName, $authorEmail);
$article = new Article($title, $content, $author);
foreach ($tags as $tagName) {
$article->addTag(new Tag($tagName));
}
return $this->repository->save($article);
}What's wrong with this code? Everything:
- No type safety until deep inside the method
- Structure is hidden in implementation details
- The relationship between form fields is implicit
Ray.InputQuery introduces a radical idea: treat input as a first-class citizen in your application architecture.
Instead of treating input as raw data to be processed, we define input as structured, typed objects that mirror our forms and API contracts:
final class ArticleInput
{
public function __construct(
#[Input] public readonly string $title,
#[Input] public readonly string $content,
#[Input] public readonly AuthorInput $author,
#[Input] public readonly array $tags = []
) {}
}
final class AuthorInput
{
public function __construct(
#[Input] public readonly string $name,
#[Input] public readonly string $email
) {}
}Now our endpoint becomes:
public function createArticle(ArticleInput $input)
{
return $this->repository->save(
Article::fromInput($input)
);
}Behind the scenes, Ray.InputQuery:
- Analyzes the method parameters
- Finds the
ArticleInputparameter with#[Input]attribute - Creates the object from query data
- Passes it to your method
Here's where it gets interesting. Your HTML form sends flat data:
title=My+Article&content=Lorem+ipsum&author_name=John&author_email=john@example.com
Ray.InputQuery automatically transforms this into:
ArticleInput {
title: "My Article"
content: "Lorem ipsum"
author: AuthorInput {
name: "John"
email: "john@example.com"
}
}Traditional approaches have a dangerous gap:
[Untyped HTTP Request] → [Manual Validation] → [Typed Domain]
↑ ↑
Bugs enter here Safety starts here
With Input classes:
[HTTP Request] → [Input Class] → [Domain]
↑
Type safety starts HERE
Your input classes naturally mirror your forms:
<form>
<input name="title" required>
<textarea name="content" required></textarea>
<fieldset>
<legend>Author Information</legend>
<input name="author_name" required>
<input name="author_email" type="email" required>
</fieldset>
<div class="tags">
<input name="tags[]" placeholder="Tag 1">
<input name="tags[]" placeholder="Tag 2">
</div>
</form>The ArticleInput class structure directly reflects this form structure. No mental mapping required!
Input classes can express structure specific to input processing:
final class PasswordChangeInput
{
public function __construct(
#[Input] public readonly string $currentPassword,
#[Input] public readonly string $newPassword,
#[Input] public readonly string $confirmPassword
) {}
}This structure belongs to the input domain, not to the domain entity or separate classes.
Let's see how this scales to complex scenarios:
final class CheckoutInput
{
public function __construct(
#[Input] public readonly CartInput $cart,
#[Input] public readonly CustomerInput $customer,
#[Input] public readonly ShippingAddressInput $shipping,
#[Input] public readonly BillingAddressInput $billing,
#[Input] public readonly PaymentMethodInput $payment,
#[Input] public readonly ?string $couponCode = null,
#[Input] public readonly bool $giftWrap = false,
#[Input] public readonly ?string $giftMessage = null
) {}
}This single class declaration documents:
- What data the checkout process needs
- How that data is structured
- What's optional vs required
- The relationships between different pieces of data
With clear input structures, AI can help generate code:
"Generate a Ray.InputQuery class for this HTML form: [paste form HTML]"
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"title": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"author": {
"$ref": "#/definitions/AuthorInput"
}
}
}One structure, multiple representations: PHP classes, JSON Schema, TypeScript types, API documentation.
// Auto-generated from PHP Input classes
interface ArticleInput {
title: string;
content: string;
author: AuthorInput;
tags: string[];
}This isn't just about a new way to handle form data. It's about recognizing that input is a fundamental concern of web applications that deserves first-class treatment.
Traditional thinking:
- "How do I transform this array into objects?"
- "What is the structure of this input?"
Input-first thinking:
- "What is the structure of this input?"
- "What constraints are inherent to this input?"
- "How does this input relate to my domain?"
composer require ray/input-queryDefine your first input class:
final class ContactInput
{
public function __construct(
#[Input] public readonly string $name,
#[Input] public readonly string $email,
#[Input] public readonly string $message
) {}
}Use it in your application:
// Method that accepts input
public function contact(ContactInput $input): Response
{
$this->mailer->send(new ContactEmail($input));
return new Response('Message sent!');
}
// Ray.InputQuery generates the argument
$inputQuery = new InputQuery($injector);
$method = new ReflectionMethod($this, 'contact');
$args = $inputQuery->getArguments($method, $_POST);
// Call the method
$response = $this->contact(...$args);Ray.InputQuery isn't just another validation library. It's a fundamental rethinking of how we handle input in web applications. By treating input as a first-class citizen, we can:
- Catch errors at the system boundary
- Write more maintainable code
- Create better developer experiences
- Build more reliable applications
The next time you write $request->input('field'), ask yourself: "What if this input had a proper home?"
Welcome to the world where input is a first-class citizen.
Ray.InputQuery is part of the Ray. family of libraries, focusing on clean, type-safe PHP development. Learn more at https://github.com/ray-di/Ray.InputQuery*