# 🔗 Nested DTOs Laravel Arc supports complex data structures with nested DTOs, allowing you to build sophisticated data models with type-safe relationships and hierarchical data. ## Understanding Nested DTOs Nested DTOs let you compose complex data structures from simpler DTOs: ``` OrderDTO ├── CustomerDTO (single nested DTO) ├── OrderItemDTO[] (collection of DTOs) └── AddressDTO (single nested DTO) ``` This creates a type-safe, validated hierarchy of data objects. ## Basic Nested DTO Example ### Simple Profile Nesting ```yaml # resources/arc/user-with-profile.yaml header: dto: UserDTO namespace: App\DTO traits: [HasTimestamps] fields: name: type: string required: true email: type: email required: true # Nested DTO profile: type: dto dto: ProfileDTO required: false ``` ```yaml # resources/arc/profile.yaml header: dto: ProfileDTO namespace: App\DTO fields: bio: type: text max_length: 500 avatar_url: type: url nullable: true birth_date: type: date nullable: true ``` ## Complex Nested Example From `examples/nested-order.yaml` - a real e-commerce order structure: ```yaml # Comprehensive nested DTO example demonstrating complex relationships header: dto: OrderDTO table: orders model: App\Models\Order namespace: App\DTO\Ecommerce traits: - HasTimestamps - HasUuid - HasSoftDeletes - HasVersioning - HasAuditing fields: order_number: type: string required: true validation: [required, string, unique:orders, size:12] status: type: enum values: [pending, processing, shipped, delivered, cancelled, refunded] default: pending # Customer as nested DTO customer: type: dto dto: CustomerDTO required: true # Collection of order items items: type: array items: type: dto dto: OrderItemDTO min_items: 1 # Billing address billing_address: type: dto dto: AddressDTO required: true # Optional shipping address shipping_address: type: dto dto: AddressDTO required: false # Payment information payment: type: dto dto: PaymentDTO required: true # Totals subtotal: type: decimal precision: 10 scale: 2 tax_amount: type: decimal precision: 10 scale: 2 total_amount: type: decimal precision: 10 scale: 2 ``` ### Supporting DTOs #### CustomerDTO ```yaml # examples/nested-customer.yaml header: dto: CustomerDTO namespace: App\DTO\Ecommerce traits: [HasTimestamps] fields: customer_id: type: integer required: true name: type: string required: true validation: [required, string, max:255] email: type: email required: true phone: type: string nullable: true validation: [nullable, string, max:20] ``` #### AddressDTO ```yaml # examples/nested-address.yaml header: dto: AddressDTO namespace: App\DTO\Ecommerce fields: street: type: string required: true max_length: 255 city: type: string required: true max_length: 100 state: type: string required: true max_length: 100 postal_code: type: string required: true pattern: "^[0-9]{5}(-[0-9]{4})?$" country: type: dto dto: CountryDTO required: true ``` ## Generated Nested DTO Structure The nested order example generates this PHP structure: ```php ['required', 'string', 'unique:orders', 'size:12'], 'status' => ['required', 'in:pending,processing,shipped,delivered,cancelled,refunded'], 'customer' => ['required', 'array'], 'customer.customer_id' => ['required', 'integer'], 'customer.name' => ['required', 'string', 'max:255'], 'customer.email' => ['required', 'email'], 'items' => ['required', 'array', 'min:1'], 'items.*.product_id' => ['required', 'integer'], 'items.*.quantity' => ['required', 'integer', 'min:1'], 'billing_address' => ['required', 'array'], 'billing_address.street' => ['required', 'string', 'max:255'], // ... more nested validation ]; } } ``` ## Creating Nested DTOs ### From Arrays ```php $orderData = [ 'order_number' => 'ORD-123456789', 'status' => 'pending', 'customer' => [ 'customer_id' => 1, 'name' => 'John Doe', 'email' => 'john@example.com', 'phone' => '+1-555-123-4567' ], 'items' => [ [ 'product_id' => 101, 'product_name' => 'Laptop', 'quantity' => 1, 'unit_price' => 999.99 ], [ 'product_id' => 102, 'product_name' => 'Mouse', 'quantity' => 2, 'unit_price' => 29.99 ] ], 'billing_address' => [ 'street' => '123 Main St', 'city' => 'Boston', 'state' => 'MA', 'postal_code' => '02101', 'country' => [ 'code' => 'US', 'name' => 'United States' ] ], 'subtotal' => 1059.97, 'tax_amount' => 84.80, 'total_amount' => 1144.77 ]; // Laravel Arc automatically creates nested DTOs $order = OrderDTO::from($orderData); // Access nested data with full type safety echo $order->customer->name; // "John Doe" echo $order->billing_address->city; // "Boston" echo $order->billing_address->country->name; // "United States" echo $order->items->first()->product_name; // "Laptop" ``` ### From Eloquent Models ```php // Load order with relationships $orderModel = Order::with([ 'customer', 'items.product', 'billingAddress.country', 'shippingAddress.country', 'payment' ])->find(1); // Convert to nested DTO $orderDto = OrderDTO::fromModel($orderModel); // Type-safe access to all nested data $customerName = $orderDto->customer->name; $itemCount = $orderDto->items->count(); $shippingCountry = $orderDto->shipping_address?->country->name; ``` ## Working with Collections of Nested DTOs ### Processing Order Items ```php $order = OrderDTO::from($orderData); // Calculate totals using collection methods $totalItems = $order->items->sum('quantity'); $heaviestItem = $order->items->max('weight'); // Filter and transform items $expensiveItems = $order->items ->filter(fn($item) => $item->unit_price > 100) ->map(fn($item) => [ 'name' => $item->product_name, 'total' => $item->quantity * $item->unit_price ]); // Group items by category $itemsByCategory = $order->items->groupBy('category'); ``` ### Bulk Operations ```php // Update all item quantities $updatedItems = $order->items->map(function($item) { return $item->with(['quantity' => $item->quantity + 1]); }); $updatedOrder = $order->with(['items' => $updatedItems]); ``` ## Validation of Nested DTOs ### Automatic Nested Validation Laravel Arc automatically validates nested structures: ```php $invalidOrderData = [ 'order_number' => 'INVALID', // Wrong format 'customer' => [ 'email' => 'not-an-email' // Invalid email ], 'items' => [], // Empty array (min:1 required) 'billing_address' => [ 'postal_code' => 'INVALID' // Wrong pattern ] ]; try { $order = OrderDTO::fromValidated($invalidOrderData); } catch (ValidationException $e) { // Errors for all nested validation failures $errors = $e->errors(); /* [ 'order_number' => ['The order number must be 12 characters.'], 'customer.email' => ['The customer email must be a valid email address.'], 'items' => ['The items field must have at least 1 items.'], 'billing_address.postal_code' => ['The postal code format is invalid.'] ] */ } ``` ### Custom Nested Validation ```php // Add custom validation for business rules class OrderDTO extends BaseDTO { public static function validationRules(): array { return array_merge(parent::validationRules(), [ 'items' => ['required', 'array', 'min:1', 'custom_order_items'], 'total_amount' => ['required', 'numeric', 'custom_total_matches_items'], ]); } public function customOrderItemsValidation($attribute, $value, $fail) { $totalCalculated = collect($value)->sum(fn($item) => $item['quantity'] * $item['unit_price'] ); if (abs($totalCalculated - $this->subtotal) > 0.01) { $fail('The order subtotal does not match item totals.'); } } } ``` ## Performance Considerations ### Lazy Loading Nested DTOs ```php // For large nested structures, consider lazy loading class OrderDTO extends BaseDTO { private ?Collection $_items = null; public function getItems(): Collection { return $this->_items ??= collect($this->itemsData) ->map(fn($item) => OrderItemDTO::from($item)); } } ``` ### Selective Nesting ```php // Only load what you need $orderSummary = OrderDTO::from($orderData)->only([ 'order_number', 'status', 'customer.name', 'total_amount' ]); ``` ## Export with Nested Data ### JSON Export ```php $order = OrderDTO::from($orderData); $json = $order->toJson(); // Automatically includes all nested DTO data ``` ### Custom Export Format ```php $orderSummary = [ 'order' => $order->only(['order_number', 'status', 'total_amount']), 'customer' => $order->customer->only(['name', 'email']), 'items_count' => $order->items->count(), 'shipping_address' => $order->shipping_address?->toArray(), ]; ``` ## Real-World Usage Patterns ### API Responses ```php // API controller returning nested order data public function show(Order $order): JsonResponse { $orderDto = OrderDTO::fromModel($order); return response()->json([ 'order' => $orderDto->toArray(), 'meta' => [ 'items_count' => $orderDto->items->count(), 'has_shipping_address' => $orderDto->shipping_address !== null, 'estimated_delivery' => $orderDto->calculateDeliveryDate(), ] ]); } ``` ### Form Processing ```php // Handle complex form submissions public function store(Request $request): JsonResponse { try { // Validate entire nested structure $orderDto = OrderDTO::fromValidated($request->all()); // Business logic with type-safe access $order = $this->orderService->createOrder($orderDto); return response()->json([ 'message' => 'Order created successfully', 'order' => $orderDto->toArray() ], 201); } catch (ValidationException $e) { return response()->json([ 'message' => 'Validation failed', 'errors' => $e->errors() ], 422); } } ``` ### Data Transformation ```php // Transform between different nested structures $legacyOrderData = $legacyApiService->getOrder($orderId); $modernOrderDto = OrderDTO::from($legacyOrderData); // Convert to new API format $newApiData = $modernOrderDto->transformTo(NewOrderFormatDTO::class); ``` ## What's Next? Now that you understand nested DTOs: - **[Collections](Collections.md)** - Working with arrays of DTOs - **[Generated DTO Structure](Generated-DTO-Structure.md)** - Understanding generated code - **[Example API Integration](Example-API-Integration.md)** - Real-world nested DTO usage - **[Field Types](Field-Types.md)** - All available field types for nesting --- *Nested DTOs provide type-safe composition of complex data structures, making your code more maintainable and less error-prone.* 🔗