Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
••••••••••••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

DDD Design Approach(PHP): Why Your Code Turns Into Spaghetti (And How to Fix It)

TL;DR 💡 Clean Architecture isn't about folders — it's a mindset ⚠️ 73% of projects die from technical debt ✅ 4 layers will save your project from a $100k refact…

Beitrag
0
Seite
0
↗ Quelle (dev.to)
Social ReaktionenReagiere als Erste:r — dein Feedback zählt!




TL;DR



💡 Clean Architecture isn't about folders — it's a mindset


⚠️ 73% of projects die from technical debt


✅ 4 layers will save your project from a $100k refactor


🎁 Ready-to-use folder structure at the end



━━━━━━━━━━━━━━━━━━━━━━━━━━━━━





😱 The Problem: Why Everything Gets Worse



I remember my first "real" project. After 6 months, I was scared to touch my own code. Adding a field to a form? 2 hours. Changing discount logic? A prayer and 3 hours.



Sound familiar? Here are typical "spaghetti code" symptoms:



⚠️ Common Mistakes:




  • 500-line controllers (hello, God Object!)

  • Business logic inside SQL queries

  • "Quick fixes" directly in templates

  • Impossible to test without a database




💬 "Good architecture costs more upfront. Bad architecture costs 10x more later."

— Every senior dev's experience




━━━━━━━━━━━━━━━━━━━━━━━━━━━━━





🎯 The Solution: Clean Architecture Explained





What is it anyway?



Clean Architecture by Robert Martin ("Uncle Bob") isn't about Symfony or Laravel. It's about the Dependency Rule:



🔺 Outer layers depend on inner layers. Never the other way around.




┌─────────────────────────────────┐
│ Presentation (UI, API) │ ← knows about Application
├─────────────────────────────────┤
│ Application (Use Cases) │ ← knows about Domain
├─────────────────────────────────┤
│ Domain (Business Logic) │ ← knows NOTHING!
├─────────────────────────────────┤
│ Infrastructure (DB, HTTP) │ ← implements Domain interfaces
└─────────────────────────────────┘









🏗️ The 4 Layers Simplified



1️⃣ Domain Layer — Business Core



This is your application's heart. Entities that your business can't exist without.




// Domain/Entity/Order.php
final class Order
{
private OrderId $id;
private Money $total;
private OrderStatus $status;

public static function create(Email $customerEmail, Money $total): self
{
$order = new self(OrderId::generate(), $total);
$order->recordEvent(new OrderCreated($order->id));
return $order;
}

// ❌ NO mentions of Doctrine, HTTP, JSON!
}






💡 Key Idea: Domain doesn't know about databases, APIs, or frameworks.



2️⃣ Application Layer — The Orchestrator



Here live Use Cases (user scenarios). Receive request → call domain → return result.




// Application/UseCase/CreateOrder/CreateOrderHandler.php
final class CreateOrderHandler
{
public function __construct(
private OrderRepositoryInterface $repo,
private EventBus $eventBus
) {}

public function __invoke(CreateOrderCommand $cmd): CreateOrderResponse
{
// 1. Validate input
$email = Email::fromString($cmd->customerEmail);
$total = Money::from($cmd->total, 'USD');

// 2. Execute business logic
$order = Order::create($email, $total);

// 3. Persist
$this->repo->save($order);
$this->eventBus->dispatch(new OrderCreated($order->getId()));

return new CreateOrderResponse($order->getId());
}
}






✅ Notice: Handler coordinates but doesn't contain business rules.



3️⃣ Infrastructure Layer — The Dirty Work



Doctrine, Redis, HTTP clients, email services. All "technical stuff".




// Infrastructure/Repository/DoctrineOrderRepository.php
final class DoctrineOrderRepository implements OrderRepositoryInterface
{
public function save(Order $order): void
{
// Map Domain Entity → Doctrine Entity
$entity = $this->mapper->toDoctrineEntity($order);
$this->entityManager->persist($entity);
$this->entityManager->flush();
}
}






4️⃣ Presentation Layer — Entry Point



Controllers, CLI commands, GraphQL resolvers.




// Infrastructure/Presentation/Controller/CreateOrderController.php
final class CreateOrderController
{
public function __invoke(
Request $request,
CreateOrderHandler $handler
): JsonResponse {
$cmd = new CreateOrderCommand(
customerEmail: $request->get('email'),
items: $request->get('items')
);

$response = $handler($cmd);

return $this->json(['orderId' => $response->orderId], 201);
}
}






━━━━━━━━━━━━━━━━━━━━━━━━━━━━━






🔥 Real Example: Before vs After






❌ Before (Classic MVC Nightmare)






// Controller doing EVERYTHING
class OrderController
{
public function create(Request $request)
{
// Validation in controller 😱
if (!filter_var($request->get('email'), FILTER_VALIDATE_EMAIL)) {
return $this->json(['error' => 'Invalid email'], 400);
}

// Business logic in controller 😱
$total = 0;
foreach ($request->get('items') as $item) {
$total += $item['price'] * $item['qty'];
}

// Direct DB access 😱
$order = new Order();
$order->setEmail($request->get('email'));
$order->setTotal($total);
$this->em->persist($order);
$this->em->flush();

// Email sending in controller 😱
$this->mailer->send('Order created!');

return $this->json($order);
}
}






Problems:

⚠️ Can't test without HTTP request


⚠️ Can't reuse logic in CLI commands


⚠️ Business rules scattered everywhere


⚠️ Changing email library breaks the controller






✅ After (Clean Architecture)






// Controller: just input/output
final class CreateOrderController
{
public function __invoke(Request $req, CreateOrderHandler $handler): JsonResponse
{
return $this->json(
$handler(new CreateOrderCommand($req->get('email'), $req->get('items')))
);
}
}

// Handler: orchestration
final class CreateOrderHandler
{
public function __invoke(CreateOrderCommand $cmd): OrderResponse
{
$order = Order::create(
Email::fromString($cmd->email),
$this->calculator->calculateTotal($cmd->items)
);

$this->repo->save($order);
$this->eventBus->dispatch(new OrderCreated($order->getId()));

return OrderResponse::fromDomain($order);
}
}

// Domain: pure business logic
final class Order
{
public static function create(Email $email, Money $total): self
{
if ($total->isNegative()) {
throw new InvalidOrderException('Total cannot be negative');
}
return new self(OrderId::generate(), $email, $total);
}
}






Benefits:

✅ Test Handler without HTTP


✅ Reuse in CLI: php bin/console order:create


✅ Business rules in one place (Domain)


✅ Swap Doctrine for MongoDB? Change only Infrastructure



━━━━━━━━━━━━━━━━━━━━━━━━━━━━━






📊 When NOT to Use Clean Architecture?



Let me be honest: Clean Architecture is always worth it. But not every client pays for quality.



🎯 Skip it if:




  • Prototype with 2-week lifespan

  • Client refuses to pay for proper architecture

  • Solo hackathon project



🔥 Use it if:




  • Project will live 1+ years

  • Team has 2+ developers

  • Business logic is complex

  • You care about your sanity




💬 "Clean Architecture is like brushing teeth. Skipping once won't kill you. Skipping always will."




━━━━━━━━━━━━━━━━━━━━━━━━━━━━━






🎁 Ready-to-Use Folder Structure






src/BoundedContext/OrderProcessing/
├── Domain/
│ ├── Entity/
│ │ └── Order.php
│ ├── ValueObject/
│ │ ├── Money.php
│ │ ├── Email.php
│ │ └── OrderId.php
│ ├── Repository/
│ │ └── OrderRepositoryInterface.php
│ └── Event/
│ └── OrderCreated.php
│
├── Application/
│ └── UseCase/
│ ├── CreateOrder/
│ │ ├── CreateOrderCommand.php
│ │ ├── CreateOrderHandler.php
│ │ └── CreateOrderResponse.php
│ └── GetOrder/
│ ├── GetOrderQuery.php
│ └── GetOrderHandler.php
│
└── Infrastructure/
├── Repository/
│ └── DoctrineOrderRepository.php
├── Mapper/
│ └── OrderMapper.php
└── Presentation/
└── Controller/
└── CreateOrderController.php






━━━━━━━━━━━━━━━━━━━━━━━━━━━━━






🚀 Action Plan: Start Today



Week 1: Create Domain folder, move entities


Week 2: Extract business logic from controllers


Week 3: Create Use Case handlers


Week 4: Introduce Repository interfaces



Don't refactor everything at once! New features — new architecture. Old code can wait.



━━━━━━━━━━━━━━━━━━━━━━━━━━━━━






💬 Your Turn



What's stopping you from implementing Clean Architecture? Is it time, team pushback, or "legacy code too big"?



Share your biggest architecture pain point in the comments! 👇



Found this useful? Follow for more software architecture deep dives.






CleanArchitecture #SoftwareDesign #DDD #PHP #Symfony #SoftwareEngineering

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten DDD Design Approach(PHP): Why Your Code Turns Into Spaghetti (And How to Fix It)

Thematisch verwandte Begriffe: Design, ApproachPHP, Your, Code · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag