# Data Transfer Objects (DTOs)

This guide explains the DTO pattern used throughout the application for transferring data between layers.

## What is a DTO?

A **Data Transfer Object (DTO)** is a simple object that carries data between processes. In our architecture, DTOs:

1. **Transfer validated request data** from controllers to services
2. **Provide type safety** with strict property typing
3. **Ensure immutability** - data cannot be changed after creation
4. **Decouple layers** - services don't depend on HTTP request structure

## Why Use DTOs?

| Without DTO | With DTO |
|-------------|----------|
| Service receives raw array | Service receives typed object |
| No autocomplete in IDE | Full IDE support |
| Easy to pass wrong data | Type errors caught early |
| Array structure can vary | Consistent data structure |
| Hard to refactor | Easy to refactor |

## DTO Interface

All DTOs implement the standard interface:

**Location:** `app/Core/Interfaces/DTOInterface.php`

```php
<?php

namespace App\Core\Interfaces;

interface DTOInterface
{
    public static function fromRequest(array $array);
}
```

## Standard DTO Template

```php
<?php

namespace App\Domains\YourDomain\YourModule\DTOs;

use App\Core\Interfaces\DTOInterface;

readonly final class YourEntityDTO implements DTOInterface
{
    public function __construct(
        public ?int $id = null,
        public ?string $name = null,
        public ?string $code = null,
        public ?string $description = null,
        public ?string $status = null,
        public ?float $amount = null,
        public ?int $quantity = null,
        public ?string $date = null,
        public ?bool $is_active = null,
        public ?int $category_id = null,
        public ?array $items = null,
        public ?array $metadata = null,
    ) {}

    public static function fromRequest(array $array): self
    {
        return new self(
            id: $array['id'] ?? null,
            name: $array['name'] ?? null,
            code: $array['code'] ?? null,
            description: $array['description'] ?? null,
            status: $array['status'] ?? null,
            amount: isset($array['amount']) ? (float) $array['amount'] : null,
            quantity: isset($array['quantity']) ? (int) $array['quantity'] : null,
            date: $array['date'] ?? null,
            is_active: isset($array['is_active']) ? (bool) $array['is_active'] : null,
            category_id: isset($array['category_id']) ? (int) $array['category_id'] : null,
            items: $array['items'] ?? null,
            metadata: $array['metadata'] ?? null,
        );
    }
}
```

## Key Characteristics

### 1. Readonly Final Class

```php
readonly final class YourEntityDTO
```

- `readonly` - All properties are immutable after construction
- `final` - Class cannot be extended

### 2. Constructor Property Promotion

```php
public function __construct(
    public ?string $name = null,
    public ?float $amount = null,
)
```

Properties are declared directly in the constructor, making them class properties.

### 3. Nullable Properties with Defaults

```php
public ?string $name = null,
public ?int $quantity = null,
```

All properties are nullable with `null` defaults to handle optional request data.

### 4. Static Factory Method

```php
public static function fromRequest(array $array): self
{
    return new self(
        name: $array['name'] ?? null,
        // ...
    );
}
```

The `fromRequest()` method creates a DTO from validated request data.

## Type Casting in fromRequest()

### Basic Types

```php
// String (no casting needed)
name: $array['name'] ?? null,

// Integer
quantity: isset($array['quantity']) ? (int) $array['quantity'] : null,

// Float/Decimal
amount: isset($array['amount']) ? (float) $array['amount'] : null,

// Boolean
is_active: isset($array['is_active']) ? (bool) $array['is_active'] : null,

// Array
items: $array['items'] ?? null,
```

### Enum Conversion

```php
use App\Domains\YourDomain\YourModule\Enums\YourEntityStatus;

readonly final class YourEntityDTO implements DTOInterface
{
    public function __construct(
        public ?YourEntityStatus $status = null,
    ) {}

    public static function fromRequest(array $array): self
    {
        return new self(
            status: isset($array['status'])
                ? YourEntityStatus::from($array['status'])
                : null,
        );
    }
}
```

### Nested DTOs

```php
readonly final class OrderDTO implements DTOInterface
{
    public function __construct(
        public ?string $order_number = null,
        public ?array $items = null,  // Array of OrderItemDTO
    ) {}

    public static function fromRequest(array $array): self
    {
        // Transform nested arrays into DTOs
        $items = isset($array['items'])
            ? array_map(
                fn($item) => OrderItemDTO::fromRequest($item),
                $array['items']
            )
            : null;

        return new self(
            order_number: $array['order_number'] ?? null,
            items: $items,
        );
    }
}
```

### Calculated Fields

```php
public static function fromRequest(array $array): self
{
    // Calculate derived values
    $installmentsCount = $array['installments_count'] ?? null;

    if (!$installmentsCount && isset($array['amount'], $array['installment_amount'])) {
        $installmentsCount = (int) ceil(
            $array['amount'] / $array['installment_amount']
        );
    }

    return new self(
        amount: isset($array['amount']) ? (float) $array['amount'] : null,
        installments_count: $installmentsCount,
    );
}
```

## Advanced DTO Patterns

### DTO with Validation Method

```php
readonly final class ApproverDTO implements DTOInterface
{
    public function __construct(
        public ?int $labor_category_id = null,
        public ?int $user_id = null,
        public ?int $order = null,
    ) {}

    public static function fromRequest(array $array): self
    {
        return new self(
            labor_category_id: $array['labor_category_id'] ?? null,
            user_id: $array['user_id'] ?? null,
            order: $array['order'] ?? null,
        );
    }

    /**
     * Validate DTO data
     */
    public function validate(): bool
    {
        // Must have exactly one of labor_category_id or user_id
        $hasCategory = !empty($this->labor_category_id);
        $hasUser = !empty($this->user_id);

        return ($hasCategory || $hasUser) && !($hasCategory && $hasUser);
    }
}
```

### DTO with toArray Method

```php
readonly final class YourEntityDTO implements DTOInterface
{
    // ... constructor and fromRequest

    /**
     * Convert DTO to array
     */
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'amount' => $this->amount,
            // ... other properties
        ];
    }

    /**
     * Get only non-null values
     */
    public function toArrayFiltered(): array
    {
        return array_filter($this->toArray(), fn($value) => $value !== null);
    }
}
```

### Base DTO with Inheritance

```php
// Base DTO
abstract class BaseContractDTO implements DTOInterface
{
    public function __construct(
        public ?int $id = null,
        public ?string $title = null,
        public ?string $status = null,
    ) {}

    protected static function baseFromRequest(array $array): array
    {
        return [
            'id' => $array['id'] ?? null,
            'title' => $array['title'] ?? null,
            'status' => $array['status'] ?? null,
        ];
    }
}

// Extended DTO
class ProjectContractDTO extends BaseContractDTO
{
    public function __construct(
        ?int $id = null,
        ?string $title = null,
        ?string $status = null,
        // Additional properties
        public ?int $project_id = null,
        public ?float $value = null,
    ) {
        parent::__construct($id, $title, $status);
    }

    public static function fromRequest(array $array): self
    {
        $base = self::baseFromRequest($array);

        return new self(
            id: $base['id'],
            title: $base['title'],
            status: $base['status'],
            project_id: $array['project_id'] ?? null,
            value: isset($array['value']) ? (float) $array['value'] : null,
        );
    }
}
```

## Usage in Controllers

```php
class YourEntityController extends Controller
{
    public function __construct(
        private readonly YourEntityService $service
    ) {}

    public function store(YourEntityRequest $request): JsonResponse
    {
        try {
            // 1. Get validated data
            $validated = $request->validated();

            // 2. Create DTO from validated data
            $dto = YourEntityDTO::fromRequest($validated);

            // 3. Pass DTO to service
            $entity = $this->service->store($dto);

            return $this->sendSuccessResponse(
                new YourEntityResource($entity),
                __('Created successfully')
            );
        } catch (Exception $e) {
            return $this->sendFailedResponse($e->getMessage());
        }
    }

    public function update(YourEntityRequest $request, YourEntity $entity): JsonResponse
    {
        try {
            $dto = YourEntityDTO::fromRequest($request->validated());
            $entity = $this->service->update($dto, $entity);

            return $this->sendSuccessResponse(
                new YourEntityResource($entity),
                __('Updated successfully')
            );
        } catch (Exception $e) {
            return $this->sendFailedResponse($e->getMessage());
        }
    }
}
```

## Usage in Services

```php
class YourEntityService
{
    public function store(YourEntityDTO $dto): YourEntity
    {
        return DB::transaction(function () use ($dto) {
            $entity = YourEntity::create([
                'company_id' => company()->id,
                'name' => $dto->name,
                'code' => $dto->code,
                'description' => $dto->description,
                'status' => $dto->status ?? 'draft',
                'amount' => $dto->amount ?? 0,
                'is_active' => $dto->is_active ?? true,
                'category_id' => $dto->category_id,
            ]);

            // Handle nested items
            if ($dto->items) {
                foreach ($dto->items as $itemDto) {
                    $entity->items()->create([
                        'name' => $itemDto->name,
                        'quantity' => $itemDto->quantity,
                    ]);
                }
            }

            return $entity->fresh(['items']);
        });
    }

    public function update(YourEntityDTO $dto, YourEntity $entity): YourEntity
    {
        return DB::transaction(function () use ($dto, $entity) {
            $entity->update([
                'name' => $dto->name ?? $entity->name,
                'code' => $dto->code ?? $entity->code,
                'description' => $dto->description ?? $entity->description,
                // Only update if provided
                ...($dto->status !== null ? ['status' => $dto->status] : []),
                ...($dto->amount !== null ? ['amount' => $dto->amount] : []),
            ]);

            return $entity->fresh();
        });
    }
}
```

## Best Practices

### DO:

1. **Always use `readonly final`** for immutability
2. **Implement `DTOInterface`** for consistency
3. **Make all properties nullable** with defaults
4. **Type cast in `fromRequest()`** for safety
5. **Use nested DTOs** for complex structures
6. **Add validation methods** when needed

### DON'T:

1. **Don't put business logic** in DTOs
2. **Don't make properties required** - handle in validation
3. **Don't use the DTO after creation** for modifications
4. **Don't skip the DTO layer** even for simple CRUD
5. **Don't access request directly** in services - use DTO

## Common Mistakes

### Wrong: Accessing Request in Service

```php
// Bad - service depends on HTTP layer
public function store(Request $request): YourEntity
{
    return YourEntity::create($request->all());
}
```

### Correct: Using DTO

```php
// Good - service depends on DTO
public function store(YourEntityDTO $dto): YourEntity
{
    return YourEntity::create([
        'name' => $dto->name,
        // ...
    ]);
}
```

### Wrong: Mutable DTO

```php
// Bad - properties can be changed
class YourEntityDTO
{
    public ?string $name = null;
}
```

### Correct: Immutable DTO

```php
// Good - properties are readonly
readonly final class YourEntityDTO
{
    public function __construct(
        public ?string $name = null,
    ) {}
}
```
