Skip to content

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:

  1. Extracts request data
  2. Validates against rules
  3. Casts to correct types
  4. 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 ​

  1. Use DTOs for request validation instead of manual validation
  2. Define separate DTOs for create and update operations
  3. Use nested objects for complex structures
  4. Provide default values for optional fields
  5. Document with docblocks for IDE support
  6. Keep DTOs simple - no business logic
  7. 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
});

Next Steps ​

Released under the MIT License.