Field Grouping
Group related fields together and extract them as a single array using the group() method.
Basic Usage
php
final class SearchRequest extends Request
{
#[Field(group: 'criteria')]
public ?string $search = null;
#[Field(group: 'criteria')]
public ?string $status = null;
#[Field(group: 'pagination')]
public int $page = 1;
#[Field(group: 'pagination')]
public int $perPage = 20;
}
$dto = $handler->handle(SearchRequest::class, $request);
$criteria = $dto->group('criteria');
// ['search' => '...', 'status' => '...']
$pagination = $dto->group('pagination');
// ['page' => 1, 'perPage' => 20]Flattening Behavior
The group() method returns a flat array:
- Array properties: Contents are merged into result
- Scalar properties: Added by property name
php
final class FilterRequest extends Request
{
#[Field(group: 'criteria')]
public array $search = [];
#[Field(group: 'criteria')]
public array $filters = [];
#[Field(group: 'criteria')]
public int $limit = 10;
}
// Given:
$dto->search = ['name' => ['LIKE', '%test%']];
$dto->filters = ['status' => 'active'];
$dto->limit = 20;
$criteria = $dto->group('criteria');
// Result:
// [
// 'name' => ['LIKE', '%test%'], // merged from $search
// 'status' => 'active', // merged from $filters
// 'limit' => 20 // scalar by property name
// ]Duplicate Key Protection
A LogicException is thrown if duplicate keys are detected:
php
final class ConflictRequest extends Request
{
#[Field(group: 'data')]
public array $first = []; // ['name' => 'Alice']
#[Field(group: 'data')]
public array $second = []; // ['name' => 'Bob']
}
$dto->group('data');
// LogicException: Duplicate key 'name' in group 'data' from property 'second'Uninitialized Properties
Only initialized properties are included in group results:
php
final class PartialRequest extends Request
{
#[Field(group: 'filters')]
public ?string $search = null;
#[Field(group: 'filters')]
public ?string $category; // No default, might be uninitialized
}
// If only 'search' was in request:
$dto->group('filters');
// ['search' => 'test'] // 'category' not includedNon-Existent Groups
Returns empty array for groups with no matching fields:
php
$dto->group('nonexistent');
// []Performance
Group metadata is cached per class. Multiple calls to group() reuse cached property lists:
php
// First call builds cache
$filters = $dto->group('criteria');
// Subsequent calls use cache
$filters = $dto->group('criteria');Clearing Cache
For long-running processes (Swoole, RoadRunner, Octane):
php
// Clear all cache
Request::clearGroupCache();
// Clear specific class
Request::clearGroupCache(SearchRequest::class);Practical Examples
Search Filters
php
final class ProductSearchRequest extends Request
{
#[Field(group: 'filters')]
public ?string $query = null;
#[Field(group: 'filters')]
public ?string $category = null;
#[Field(group: 'filters')]
public ?float $minPrice = null;
#[Field(group: 'filters')]
public ?float $maxPrice = null;
#[Field(group: 'sorting')]
public string $sortBy = 'created_at';
#[Field(group: 'sorting')]
public string $sortDir = 'DESC';
#[Field(group: 'pagination')]
public int $page = 1;
#[Field(group: 'pagination')]
public int $limit = 20;
}
// Usage
$filters = $dto->group('filters');
$sorting = $dto->group('sorting');
$pagination = $dto->group('pagination');
$products = $repository->search($filters, $sorting, $pagination);API Response Options
php
final class ApiRequest extends Request
{
#[Field(rules: 'required|integer')]
public int $resourceId;
#[Field(group: 'options')]
public bool $includeRelations = false;
#[Field(group: 'options')]
public bool $includeMeta = false;
#[Field(group: 'options')]
public ?string $fields = null;
}
$options = $dto->group('options');
// ['includeRelations' => true, 'includeMeta' => false, 'fields' => 'id,name']Query Builder Integration
php
final class UserListRequest extends Request
{
#[Field(group: 'where')]
public array $filters = [];
#[Field(group: 'where')]
public array $search = [];
#[Field(group: 'order')]
public string $orderBy = 'id';
#[Field(group: 'order')]
public string $orderDir = 'ASC';
}
// Build query
$query = $userRepository->query();
foreach ($dto->group('where') as $column => $value) {
$query->where($column, $value);
}
$order = $dto->group('order');
$query->orderBy($order['orderBy'], $order['orderDir']);Combining with Other Features
Groups work with all other field features:
php
final class ComplexRequest extends Request
{
#[Field(
rules: 'nullable|string',
preProcess: 'trim',
group: 'search'
)]
public ?string $query = null;
#[Field(
rules: 'in:asc,desc',
postProcess: 'strtoupper',
group: 'sorting'
)]
public string $direction = 'asc';
#[Field(
generator: TimestampGenerator::class,
group: 'meta',
exclude: true
)]
public int $requestedAt;
}