Data Objects
Overview
Data objects provide type-safe request handling with:
- Automatic validation
- Type casting
- Nested objects
- Collection handling
- Default values
Basic Usage
Define Data Object
php
use Nexphant\Support\Data;
use Nexphant\Validation\Rules\{Email, Length};
class RegisterData extends Data
{
#[Email]
public string $email;
#[Length(min: 3, max: 100)]
public string $name;
#[Length(min: 8)]
public string $password;
}Use in Controller
php
$app->post('/register', function (RegisterData $data) {
// $data is validated and typed
$userId = User::query()->insertGetId([
'email' => $data->email,
'name' => $data->name,
'password' => password_hash($data->password, PASSWORD_DEFAULT),
]);
return ['id' => $userId];
});Framework automatically:
- Extracts request data
- Validates against rules
- Casts to correct types
- Injects into controller
Validation Rules
Email
php
#[Email]
public string $email;Length
php
#[Length(min: 3, max: 100)]
public string $name;
#[Length(min: 8)]
public string $password;
#[Length(max: 255)]
public string $bio;Numeric
php
#[Min(18)]
public int $age;
#[Max(100)]
public int $quantity;
#[Between(1, 10)]
public int $rating;Pattern
php
#[Pattern('/^[a-z0-9_]+$/')]
public string $username;Required
php
#[Required]
public string $field;Enum
php
#[In(['active', 'inactive', 'pending'])]
public string $status;Optional Fields
Nullable
php
class UpdateUserData extends Data
{
public ?string $name = null;
public ?string $bio = null;
public ?int $age = null;
}Default Values
php
class SearchData extends Data
{
public string $query;
public int $page = 1;
public int $perPage = 20;
public string $sort = 'created_at';
}Nested Objects
php
class AddressData extends Data
{
public string $street;
public string $city;
public string $country;
}
class UserData extends Data
{
public string $name;
public string $email;
public AddressData $address;
}Request:
json
{
"name": "Alice",
"email": "alice@example.com",
"address": {
"street": "123 Main St",
"city": "New York",
"country": "USA"
}
}Usage:
php
$app->post('/users', function (UserData $data) {
echo $data->address->city; // "New York"
return ['success' => true];
});Collections
Array of Scalars
php
class TagsData extends Data
{
/** @var string[] */
public array $tags;
}Request:
json
{
"tags": ["php", "nexphant", "framework"]
}Array of Objects
php
class ItemData extends Data
{
public string $name;
public int $quantity;
}
class OrderData extends Data
{
/** @var ItemData[] */
public array $items;
}Request:
json
{
"items": [
{"name": "Product A", "quantity": 2},
{"name": "Product B", "quantity": 1}
]
}Usage:
php
$app->post('/orders', function (OrderData $data) {
foreach ($data->items as $item) {
echo $item->name . ': ' . $item->quantity;
}
return ['total_items' => count($data->items)];
});Type Casting
Automatic Casting
php
class CastData extends Data
{
public int $age; // "25" -> 25
public float $price; // "19.99" -> 19.99
public bool $active; // "true" -> true
public DateTimeImmutable $createdAt; // "2024-01-01" -> DateTimeImmutable
}Custom Casting
php
use Nexphant\Support\Attributes\Cast;
class ProductData extends Data
{
#[Cast('json')]
public array $metadata;
#[Cast('encrypted')]
public string $secret;
}Validation Errors
Automatic Response
Invalid data returns:
json
{
"error": "Validation failed",
"errors": {
"email": ["Invalid email format"],
"password": ["Must be at least 8 characters"]
}
}Manual Handling
php
use Nexphant\Validation\ValidationException;
$app->post('/register', function (Request $request) {
try {
$data = RegisterData::from($request->json());
// Process valid data
return ['success' => true];
} catch (ValidationException $e) {
return [
'error' => 'Validation failed',
'errors' => $e->errors(),
];
}
});Transformations
From Request
php
$data = RegisterData::from($request->json());
$data = RegisterData::from($request->all());From Array
php
$data = RegisterData::from([
'email' => 'test@example.com',
'name' => 'Test User',
'password' => 'password123',
]);To Array
php
$array = $data->toArray();To JSON
php
$json = $data->toJson();
$json = json_encode($data);Complete Examples
API Endpoint
php
class CreatePostData extends Data
{
#[Length(min: 3, max: 255)]
public string $title;
#[Length(min: 10)]
public string $content;
/** @var string[] */
public array $tags = [];
public ?string $excerpt = null;
}
$app->post('/posts', function (CreatePostData $data) {
$postId = Post::query()->insertGetId([
'title' => $data->title,
'content' => $data->content,
'excerpt' => $data->excerpt,
'tags' => json_encode($data->tags),
'user_id' => auth()->id(),
]);
return ['id' => $postId];
})->middleware('auth');Search with Filters
php
class SearchData extends Data
{
public string $query;
public int $page = 1;
public int $perPage = 20;
#[In(['created_at', 'updated_at', 'title'])]
public string $sortBy = 'created_at';
#[In(['asc', 'desc'])]
public string $sortOrder = 'desc';
/** @var string[] */
public array $tags = [];
}
$app->get('/search', function (SearchData $data) {
$query = Post::query()
->where('title', 'LIKE', '%' . $data->query . '%')
->orderBy($data->sortBy, $data->sortOrder);
if (!empty($data->tags)) {
$query->whereIn('tag', $data->tags);
}
return $query->paginate($data->page, $data->perPage);
});Form with Nested Data
php
class AddressData extends Data
{
public string $street;
public string $city;
public string $postalCode;
public string $country;
}
class PaymentData extends Data
{
#[Pattern('/^[0-9]{16}$/')]
public string $cardNumber;
#[Pattern('/^[0-9]{2}\/[0-9]{2}$/')]
public string $expiry;
#[Pattern('/^[0-9]{3,4}$/')]
public string $cvv;
}
class CheckoutData extends Data
{
public AddressData $shipping;
public AddressData $billing;
public PaymentData $payment;
/** @var ItemData[] */
public array $items;
}
$app->post('/checkout', function (CheckoutData $data) {
// All nested data validated and typed
$orderId = Order::query()->insertGetId([
'shipping_address' => json_encode($data->shipping),
'billing_address' => json_encode($data->billing),
'items' => json_encode($data->items),
'user_id' => auth()->id(),
]);
// Process payment with $data->payment
return ['order_id' => $orderId];
})->middleware('auth');Best Practices
- Use DTOs for request validation instead of manual validation
- Define separate DTOs for create and update operations
- Use nested objects for complex structures
- Provide default values for optional fields
- Document with docblocks for IDE support
- Keep DTOs simple - no business logic
- Use type hints for better IDE autocomplete
vs Manual Validation
Manual
php
$app->post('/register', function (Request $request) {
$data = $request->json();
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
return ['error' => 'Invalid email'];
}
if (strlen($data['password']) < 8) {
return ['error' => 'Password too short'];
}
// Process...
});With Data Object
php
class RegisterData extends Data
{
#[Email]
public string $email;
#[Length(min: 8)]
public string $password;
}
$app->post('/register', function (RegisterData $data) {
// Already validated and typed
});