# DTO Pattern (Data Transfer Object)

## Overview

The DTO Pattern provides a way to transfer data between layers with type safety and validation. DTOs are immutable objects that carry data without behavior.

## Benefits

- **Type Safety**: Strongly typed properties prevent runtime errors
- **Immutability**: Prevents accidental modification after creation
- **Validation**: Centralized data transformation and validation
- **Documentation**: Self-documenting data structures
- **Decoupling**: Separates request/response formats from domain models

## Implementation Structure

```
app/Domains/{Domain}/
└── DTOs/
    └── {Entity}DTO.php
```

## DTO Interface

All DTOs implement the `DTOInterface` for consistency.

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

```php
<?php

namespace App\Core\Interfaces;

interface DTOInterface
{
    /**
     * Create DTO from request array
     */
    public static function fromRequest(array $array);
}
```

## Basic DTO Implementation

**Location**: `app/Domains/Accounting/Finance/DTOs/DefaultTreasureDTO.php`

```php
<?php

namespace App\Domains\Accounting\Finance\DTOs;

use App\Core\Interfaces\DTOInterface;

readonly final class DefaultTreasureDTO implements DTOInterface
{
    public function __construct(
        public ?int    $id = null,
        public ?int    $user_id = null,
        public ?int    $treasure_id = null,
        public ?string $created_at = null,
        public ?string $updated_at = null,
    ) {}

    /**
     * Create DTO from validated request data
     */
    public static function fromRequest(array $array): DefaultTreasureDTO
    {
        return new self(
            id: $array['id'] ?? null,
            treasure_id: $array['treasure_id'] ?? null,
            user_id: $array['user_id'] ?? null,
            created_at: $array['created_at'] ?? null,
            updated_at: $array['updated_at'] ?? null,
        );
    }

    /**
     * Convert DTO to array for database operations
     */
    public function toArray(): array
    {
        return array_filter([
            'id' => $this->id,
            'user_id' => $this->user_id,
            'treasure_id' => $this->treasure_id,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ], fn($value) => $value !== null);
    }
}
```

## Complex DTO with Nested Data

**Location**: `app/Domains/Construction/Project/DTOs/ProjectFileDTO.php`

```php
<?php

namespace App\Domains\Construction\Project\DTOs;

use App\Core\Interfaces\DTOInterface;

readonly final class ProjectFileDTO implements DTOInterface
{
    public function __construct(
        public ?int    $id = null,
        public ?int    $project_id = null,
        public ?string $file_name = null,
        public ?string $file_path = null,
        public ?string $file_type = null,
        public ?int    $file_size = null,
        public ?string $description = null,
        public ?int    $uploaded_by = null,
        public ?string $created_at = null,
        public ?string $updated_at = null,
    ) {}

    public static function fromRequest(array $array): ProjectFileDTO
    {
        return new self(
            id: $array['id'] ?? null,
            project_id: $array['project_id'] ?? null,
            file_name: $array['file_name'] ?? null,
            file_path: $array['file_path'] ?? null,
            file_type: $array['file_type'] ?? null,
            file_size: $array['file_size'] ?? null,
            description: $array['description'] ?? null,
            uploaded_by: $array['uploaded_by'] ?? auth()->id(),
            created_at: $array['created_at'] ?? null,
            updated_at: $array['updated_at'] ?? null,
        );
    }

    /**
     * Create DTO from uploaded file
     */
    public static function fromUploadedFile(
        \Illuminate\Http\UploadedFile $file,
        int $projectId,
        ?string $description = null
    ): ProjectFileDTO {
        return new self(
            project_id: $projectId,
            file_name: $file->getClientOriginalName(),
            file_path: $file->store('projects/' . $projectId, 'public'),
            file_type: $file->getMimeType(),
            file_size: $file->getSize(),
            description: $description,
            uploaded_by: auth()->id(),
        );
    }

    public function toArray(): array
    {
        return array_filter([
            'id' => $this->id,
            'project_id' => $this->project_id,
            'file_name' => $this->file_name,
            'file_path' => $this->file_path,
            'file_type' => $this->file_type,
            'file_size' => $this->file_size,
            'description' => $this->description,
            'uploaded_by' => $this->uploaded_by,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ], fn($value) => $value !== null);
    }
}
```

## DTO with Computed Properties

```php
<?php

namespace App\Domains\Accounting\Finance\DTOs;

use App\Core\Interfaces\DTOInterface;

readonly final class InvoiceDTO implements DTOInterface
{
    public function __construct(
        public ?int    $id = null,
        public ?int    $customer_id = null,
        public ?string $invoice_number = null,
        public ?float  $subtotal = null,
        public ?float  $tax_rate = null,
        public ?float  $discount = null,
        public ?string $status = null,
        public ?string $due_date = null,
        public ?array  $items = null,
    ) {}

    public static function fromRequest(array $array): InvoiceDTO
    {
        return new self(
            id: $array['id'] ?? null,
            customer_id: $array['customer_id'] ?? null,
            invoice_number: $array['invoice_number'] ?? null,
            subtotal: $array['subtotal'] ?? null,
            tax_rate: $array['tax_rate'] ?? 0.0,
            discount: $array['discount'] ?? 0.0,
            status: $array['status'] ?? 'draft',
            due_date: $array['due_date'] ?? null,
            items: $array['items'] ?? [],
        );
    }

    /**
     * Calculate tax amount
     */
    public function taxAmount(): float
    {
        return ($this->subtotal ?? 0) * ($this->tax_rate ?? 0) / 100;
    }

    /**
     * Calculate total amount
     */
    public function totalAmount(): float
    {
        return ($this->subtotal ?? 0) + $this->taxAmount() - ($this->discount ?? 0);
    }

    /**
     * Check if invoice is overdue
     */
    public function isOverdue(): bool
    {
        if (!$this->due_date) {
            return false;
        }

        return now()->greaterThan($this->due_date);
    }

    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'customer_id' => $this->customer_id,
            'invoice_number' => $this->invoice_number,
            'subtotal' => $this->subtotal,
            'tax_rate' => $this->tax_rate,
            'tax_amount' => $this->taxAmount(),
            'discount' => $this->discount,
            'total_amount' => $this->totalAmount(),
            'status' => $this->status,
            'due_date' => $this->due_date,
        ];
    }

    /**
     * Get items for database insertion
     */
    public function itemsForDatabase(): array
    {
        return array_map(fn($item) => [
            'product_id' => $item['product_id'],
            'quantity' => $item['quantity'],
            'unit_price' => $item['unit_price'],
            'total' => $item['quantity'] * $item['unit_price'],
        ], $this->items ?? []);
    }
}
```

## DTO for Automation Rules

**Location**: `app/Domains/Core/AutomationRule/DTOs/AutomationRuleDTO.php`

```php
<?php

namespace App\Domains\Core\AutomationRule\DTOs;

use App\Core\Interfaces\DTOInterface;

readonly final class AutomationRuleDTO implements DTOInterface
{
    public function __construct(
        public ?int    $id = null,
        public ?string $name = null,
        public ?string $automatable_type = null,
        public ?int    $automatable_id = null,
        public ?string $trigger_status = null,
        public ?array  $conditions = null,
        public ?array  $actions = null,
        public ?bool   $is_active = true,
    ) {}

    public static function fromRequest(array $array): AutomationRuleDTO
    {
        return new self(
            id: $array['id'] ?? null,
            name: $array['name'] ?? null,
            automatable_type: $array['automatable_type'] ?? null,
            automatable_id: $array['automatable_id'] ?? null,
            trigger_status: $array['trigger_status'] ?? null,
            conditions: $array['conditions'] ?? [],
            actions: $array['actions'] ?? [],
            is_active: $array['is_active'] ?? true,
        );
    }

    /**
     * Get the fully qualified model class name
     */
    public function getModelClass(): string
    {
        return match($this->automatable_type) {
            'invoice' => \App\Domains\Accounting\Sales\Models\Invoice::class,
            'order' => \App\Domains\Procurement\Models\Order::class,
            'project' => \App\Domains\Construction\Project\Models\Project::class,
            default => throw new \InvalidArgumentException('Unknown automatable type'),
        };
    }

    public function toArray(): array
    {
        return array_filter([
            'id' => $this->id,
            'name' => $this->name,
            'automatable_type' => $this->automatable_type,
            'automatable_id' => $this->automatable_id,
            'trigger_status' => $this->trigger_status,
            'conditions' => $this->conditions,
            'actions' => $this->actions,
            'is_active' => $this->is_active,
        ], fn($value) => $value !== null);
    }
}
```

## Usage in Controllers

```php
<?php

namespace App\Http\Controllers\V1\Accounting;

use App\Domains\Accounting\Finance\DTOs\InvoiceDTO;
use App\Domains\Accounting\Finance\Services\InvoiceService;
use App\Http\Controllers\Controller;
use App\Http\Requests\V1\Accounting\StoreInvoiceRequest;
use Illuminate\Http\JsonResponse;

class InvoiceController extends Controller
{
    public function __construct(
        private InvoiceService $service
    ) {}

    public function store(StoreInvoiceRequest $request): JsonResponse
    {
        // Create DTO from validated request
        $dto = InvoiceDTO::fromRequest($request->validated());

        // Pass DTO to service
        $invoice = $this->service->create($dto);

        return $this->sendSuccessResponse(
            data: new InvoiceResource($invoice),
            message: __('Invoice created successfully'),
            code: 201
        );
    }
}
```

## Usage in Services

```php
<?php

namespace App\Domains\Accounting\Finance\Services;

use App\Domains\Accounting\Finance\DTOs\InvoiceDTO;
use App\Domains\Accounting\Finance\Models\Invoice;
use Illuminate\Support\Facades\DB;

class InvoiceService
{
    public function create(InvoiceDTO $dto): Invoice
    {
        return DB::transaction(function () use ($dto) {
            // Create invoice from DTO
            $invoice = Invoice::create([
                'customer_id' => $dto->customer_id,
                'invoice_number' => $dto->invoice_number,
                'subtotal' => $dto->subtotal,
                'tax_rate' => $dto->tax_rate,
                'tax_amount' => $dto->taxAmount(),
                'discount' => $dto->discount,
                'total_amount' => $dto->totalAmount(),
                'status' => $dto->status,
                'due_date' => $dto->due_date,
            ]);

            // Create items from DTO
            $invoice->items()->createMany($dto->itemsForDatabase());

            return $invoice;
        });
    }
}
```

## Creating DTO from Model

```php
<?php

readonly final class CategoryDTO implements DTOInterface
{
    // ... constructor and properties ...

    public static function fromRequest(array $array): CategoryDTO
    {
        return new self(
            id: $array['id'] ?? null,
            name: $array['name'] ?? null,
            code: $array['code'] ?? null,
            parent_id: $array['parent_id'] ?? null,
            is_active: $array['is_active'] ?? true,
        );
    }

    /**
     * Create DTO from existing model
     */
    public static function fromModel(Category $category): CategoryDTO
    {
        return new self(
            id: $category->id,
            name: $category->name,
            code: $category->code,
            parent_id: $category->parent_id,
            is_active: $category->is_active,
            created_at: $category->created_at?->toISOString(),
            updated_at: $category->updated_at?->toISOString(),
        );
    }
}
```

## DTOs in the Project

| Domain | DTO | Purpose |
|--------|-----|---------|
| Accounting | `DefaultTreasureDTO` | Default treasure settings |
| Accounting | `CategoryDTO` | Financial categories |
| Accounting | `InvoiceDTO` | Invoice data transfer |
| Construction | `ProjectFileDTO` | Project file uploads |
| Core | `AutomationRuleDTO` | Automation rules |
| Management | `EmployeeDTO` | Employee data |
| Catalog | `ProductDTO` | Product information |

## Best Practices

### Do

- Use `readonly` classes for immutability (PHP 8.2+)
- Use `final` to prevent inheritance
- Implement `DTOInterface` for consistency
- Provide factory methods (`fromRequest`, `fromModel`)
- Include `toArray()` for database operations
- Use nullable types with default `null`

### Don't

- Add behavior/business logic to DTOs
- Use DTOs for complex domain operations
- Modify DTO properties after creation
- Skip validation before creating DTOs
- Use DTOs as database models

## Related Patterns

- [Service Pattern](SERVICE_PATTERN.md) - Services consume DTOs
- [Repository Pattern](REPOSITORY_PATTERN.md) - Repositories may use DTO data
