Extending DTOs
Extend SimpleDTO with custom functionality.
Introduction
Section titled “Introduction”Extend DTOs to add custom behavior:
- ✅ Custom Methods - Add business logic
- ✅ Traits - Reuse functionality
- ✅ Inheritance - Share common properties
- ✅ Interfaces - Define contracts
Custom Methods
Section titled “Custom Methods”Adding Business Logic
Section titled “Adding Business Logic”class UserDTO extends SimpleDTO{ public function __construct( public readonly string $firstName, public readonly string $lastName, public readonly string $email, ) {}
public function getFullName(): string { return "{$this->firstName} {$this->lastName}"; }
public function getInitials(): string { return strtoupper($this->firstName[0] . $this->lastName[0]); }
public function isEmailVerified(): bool { return User::where('email', $this->email) ->whereNotNull('email_verified_at') ->exists(); }}Validation Methods
Section titled “Validation Methods”class ProductDTO extends SimpleDTO{ public function __construct( public readonly string $name, public readonly float $price, public readonly int $stock, ) {}
public function isInStock(): bool { return $this->stock > 0; }
public function isLowStock(): bool { return $this->stock > 0 && $this->stock < 10; }
public function canPurchase(int $quantity): bool { return $this->stock >= $quantity; }}Using Traits
Section titled “Using Traits”Creating a Trait
Section titled “Creating a Trait”trait Timestampable{ public readonly Carbon $createdAt; public readonly Carbon $updatedAt;
public function isNew(): bool { return $this->createdAt->diffInDays(now()) < 7; }
public function wasRecentlyUpdated(): bool { return $this->updatedAt->diffInHours(now()) < 24; }}Using the Trait
Section titled “Using the Trait”class PostDTO extends SimpleDTO{ use Timestampable;
public function __construct( public readonly string $title, public readonly string $content, public readonly Carbon $createdAt, public readonly Carbon $updatedAt, ) {}}
$post = PostDTO::fromArray($data);if ($post->isNew()) { echo 'New post!';}Inheritance
Section titled “Inheritance”Base DTO
Section titled “Base DTO”abstract class BaseDTO extends SimpleDTO{ public readonly int $id; public readonly Carbon $createdAt; public readonly Carbon $updatedAt;
public function getId(): int { return $this->id; }
public function getCreatedAt(): Carbon { return $this->createdAt; }}Extending Base DTO
Section titled “Extending Base DTO”class UserDTO extends BaseDTO{ public function __construct( public readonly int $id, public readonly string $name, public readonly string $email, public readonly Carbon $createdAt, public readonly Carbon $updatedAt, ) {}}
class PostDTO extends BaseDTO{ public function __construct( public readonly int $id, public readonly string $title, public readonly string $content, public readonly Carbon $createdAt, public readonly Carbon $updatedAt, ) {}}Implementing Interfaces
Section titled “Implementing Interfaces”Creating an Interface
Section titled “Creating an Interface”interface Searchable{ public function getSearchableFields(): array; public function getSearchWeight(): int;}Implementing the Interface
Section titled “Implementing the Interface”class ProductDTO extends SimpleDTO implements Searchable{ public function __construct( public readonly string $name, public readonly string $description, public readonly string $sku, ) {}
public function getSearchableFields(): array { return [ 'name' => $this->name, 'description' => $this->description, 'sku' => $this->sku, ]; }
public function getSearchWeight(): int { return 10; }}Real-World Examples
Section titled “Real-World Examples”User DTO with Permissions
Section titled “User DTO with Permissions”class UserDTO extends SimpleDTO{ public function __construct( public readonly int $id, public readonly string $name, public readonly string $email, public readonly string $role, public readonly array $permissions, ) {}
public function can(string $permission): bool { return in_array($permission, $this->permissions); }
public function isAdmin(): bool { return $this->role === 'admin'; }
public function isModerator(): bool { return in_array($this->role, ['admin', 'moderator']); }}Order DTO with Calculations
Section titled “Order DTO with Calculations”class OrderDTO extends SimpleDTO{ public function __construct( public readonly int $id, public readonly array $items, public readonly float $taxRate, public readonly float $shippingCost, ) {}
public function getSubtotal(): float { return array_sum(array_map( fn($item) => $item['price'] * $item['quantity'], $this->items )); }
public function getTax(): float { return $this->getSubtotal() * $this->taxRate; }
public function getTotal(): float { return $this->getSubtotal() + $this->getTax() + $this->shippingCost; }
public function getItemCount(): int { return array_sum(array_column($this->items, 'quantity')); }}Product DTO with Formatting
Section titled “Product DTO with Formatting”class ProductDTO extends SimpleDTO{ public function __construct( public readonly string $name, public readonly int $priceInCents, public readonly string $currency, ) {}
public function getFormattedPrice(): string { $price = $this->priceInCents / 100;
return match($this->currency) { 'USD' => '$' . number_format($price, 2), 'EUR' => '€' . number_format($price, 2), 'GBP' => '£' . number_format($price, 2), default => $this->currency . ' ' . number_format($price, 2), }; }
public function getPriceInDollars(): float { return $this->priceInCents / 100; }}Advanced Patterns
Section titled “Advanced Patterns”Factory Methods
Section titled “Factory Methods”class UserDTO extends SimpleDTO{ public function __construct( public readonly string $name, public readonly string $email, public readonly string $role, ) {}
public static function createAdmin(string $name, string $email): self { return new self($name, $email, 'admin'); }
public static function createUser(string $name, string $email): self { return new self($name, $email, 'user'); }}
$admin = UserDTO::createAdmin('John Doe', 'john@example.com');Builder Pattern
Section titled “Builder Pattern”class UserDTOBuilder{ private array $data = [];
public function setName(string $name): self { $this->data['name'] = $name; return $this; }
public function setEmail(string $email): self { $this->data['email'] = $email; return $this; }
public function setRole(string $role): self { $this->data['role'] = $role; return $this; }
public function build(): UserDTO { return UserDTO::fromArray($this->data); }}
$user = (new UserDTOBuilder()) ->setName('John Doe') ->setEmail('john@example.com') ->setRole('admin') ->build();Immutable Updates
Section titled “Immutable Updates”class UserDTO extends SimpleDTO{ public function __construct( public readonly string $name, public readonly string $email, ) {}
public function withName(string $name): self { return new self($name, $this->email); }
public function withEmail(string $email): self { return new self($this->name, $email); }}
$user = UserDTO::fromArray(['name' => 'John', 'email' => 'john@example.com']);$updated = $user->withName('Jane');Best Practices
Section titled “Best Practices”Single Responsibility
Section titled “Single Responsibility”// ✅ Good - single responsibilityclass UserDTO extends SimpleDTO{ public function getFullName(): string { return "{$this->firstName} {$this->lastName}"; }}
// ❌ Bad - multiple responsibilitiesclass UserDTO extends SimpleDTO{ public function getFullName(): string { } public function sendEmail(): void { } public function saveToDatabase(): void { }}Immutability
Section titled “Immutability”// ✅ Good - immutablepublic function withName(string $name): self{ return new self($name, $this->email);}
// ❌ Bad - mutablepublic function setName(string $name): void{ $this->name = $name;}Type Hints
Section titled “Type Hints”// ✅ Good - type hintspublic function getFullName(): string{ return "{$this->firstName} {$this->lastName}";}
// ❌ Bad - no type hintspublic function getFullName(){ return "{$this->firstName} {$this->lastName}";}See Also
Section titled “See Also”- SimpleDTO Introduction - DTO basics
- Custom Casts - Custom type casts
- Custom Validation - Custom validation rules