0Pricing
PHP Academy · Lekcja

Od architektury warstwowej do Clean Architecture

Proszę zrozumieć, dlaczego zależności powinny być skierowane do wnętrza.

Od architektury warstwowej do Clean Architecture to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.

Dlaczego Clean Architecture

Znają już Państwo klasyczny, trójwarstwowy stos PHP: Controller → Service → Repository → Database. Działa on, ale logika biznesowa staje się sprzężona z Eloquent, Doctrine, żądaniem HTTP i cyklem życia frameworka. Clean Architecture odwraca kierunek zależności, dzięki czemu Państwa domena nie wie nic o infrastrukturze. Korzyści to testowalne przypadki użycia, wymienne adaptery i baza kodu, która przetrwa aktualizacje frameworka.

Reguła zależności

Jedyna reguła Clean Architecture brzmi: zależności w kodzie źródłowym wskazują wyłącznie do wewnątrz. Wewnętrzne kręgi (encje, przypadki użycia) nigdy nie mogą odwoływać się do zewnętrznych kręgów (kontrolerów, ORM-ów, frameworków). W czasie działania przepływ sterowania może przebiegać na zewnątrz za pośrednictwem interfejsów, ale w czasie kompilacji/importu nic z warstwy wewnętrznej nie importuje niczego z warstwy zewnętrznej.

  • Encje: reguły biznesowe organizacji
  • Przypadki użycia: reguły aplikacji
  • Adaptery: kontrolery, prezenterzy, bramki
  • Frameworki i sterowniki: DB, HTTP, sieć

Przykład sprzężonej architektury warstwowej

Oto typ serwisu dostarczanego przez większość aplikacji PHP. Zwróćcie Państwo uwagę, że logika domenowa jest splątana z Eloquent i odpowiedzią HTTP. Nie można przetestować jednostkowo reguły rabatu bez bazy danych i frameworka.

<?php
class OrderService
{
    public function place(Request $request)
    {
        $user = User::find($request->user_id); // Eloquent
        $total = 0;
        foreach ($request->items as $i) {
            $total += Product::find($i['id'])->price * $i['qty'];
        }
        if ($user->is_vip) {
            $total *= 0.9; // business rule trapped in infra code
        }
        Order::create(['user_id' => $user->id, 'total' => $total]);
        return response()->json(['total' => $total]);
    }
}

Encje: domena niezależna od frameworka

Encja koduje reguły obowiązujące w całym przedsiębiorstwie i nie zależy od niczego. Czysty PHP, bez adnotacji i bez klasy bazowej z ORM-u. Można ją w pełni konstruować w teście.

<?php
final class Money
{
    public function __construct(public readonly int $cents) {
        if ($cents < 0) throw new InvalidArgumentException('negative money');
    }
    public function multiply(float $factor): self {
        return new self((int) round($this->cents * $factor));
    }
}

final class Order
{
    /** @param array<int,int> $lineCents */
    public function __construct(private array $lineCents, private bool $vip) {}
    public function total(): Money {
        $sum = array_sum($this->lineCents);
        $money = new Money($sum);
        return $this->vip ? $money->multiply(0.9) : $money;
    }
}

echo (new Order([1000, 2000], true))->total()->cents, PHP_EOL; // 2700

Przypadki użycia zarządzają przebiegiem

Przypadek użycia (interaktor) orkiestruje encje i komunikuje się ze światem zewnętrznym wyłącznie za pośrednictwem interfejsów (portów). Otrzymuje DTO żądania i zwraca DTO odpowiedzi — nigdy obiekt HTTP.

<?php
interface OrderRepository {
    public function save(Order $order): void;
}

final class PlaceOrder
{
    public function __construct(private OrderRepository $orders) {}

    public function execute(array $lineCents, bool $vip): int {
        $order = new Order($lineCents, $vip);
        $this->orders->save($order);
        return $order->total()->cents;
    }
}

Granica jest interfejsem

Przypadek użycia deklaruje potrzebny mu interfejs OrderRepository. Interfejs znajduje się w kręgu wewnętrznym, a konkretna implementacja Eloquent/Doctrine znajduje się na zewnątrz i zależy od środka. Jest to zasada odwrócenia zależności zastosowana na granicy architektonicznej.

Kierunek zależności w kodzie źródłowym: EloquentOrderRepository → OrderRepository (interfejs), nigdy odwrotnie.

<?php
// Lives in infrastructure layer, points INWARD to the domain interface
final class EloquentOrderRepository implements OrderRepository
{
    public function save(Order $order): void {
        OrderModel::create(['total' => $order->total()->cents]);
    }
}

Testowanie bez infrastruktury

Ponieważ przypadek użycia zależy od interfejsu, testy wstrzykują fake. Bez bazy danych i uruchamiania frameworka — błyskawiczne testy jednostkowe, które sprawdzają czyste zachowanie biznesowe.

<?php
final class InMemoryOrders implements OrderRepository {
    public array $saved = [];
    public function save(Order $o): void { $this->saved[] = $o; }
}

$repo = new InMemoryOrders();
$useCase = new PlaceOrder($repo);
$total = $useCase->execute([1000, 2000], true);

assert($total === 2700);
assert(count($repo->saved) === 1);
echo "PASS total=$total saved=" . count($repo->saved) . PHP_EOL;

Kontrolery stają się cienkimi adapterami

Kontroler jest teraz adapterem: tłumaczy HTTP na wywołanie przypadku użycia, a wynik na HTTP. Nie zawiera żadnych reguł biznesowych. Można zastąpić REST interfejsem CLI albo procesem obsługującym kolejkę, a przypadek użycia pozostanie nietknięty.

<?php
final class OrderController
{
    public function __construct(private PlaceOrder $placeOrder) {}

    public function store(Request $request): JsonResponse {
        $total = $this->placeOrder->execute(
            lineCents: $request->input('lineCents'),
            vip: (bool) $request->input('vip'),
        );
        return new JsonResponse(['total' => $total], 201);
    }
}

Architektura wyrażająca domenę

Struktura katalogów powinna wyrażać domenę, a nie framework. Należy unikać katalogów Controllers/ i Models/ na najwyższym poziomie. Organizacja według zdolności pozwala nowej osobie szybko zrozumieć, czym zajmuje się aplikacja.

  • src/Ordering/Domain/ — encje, obiekty wartości
  • src/Ordering/Application/ — przypadki użycia, interfejsy portów
  • src/Ordering/Infrastructure/ — repozytoria Eloquent, kontrolery HTTP

Każdy bounded context jest katalogiem najwyższego poziomu, a framework pozostaje na obrzeżach.

Wymuszanie reguły zależności

Dyscyplina bez narzędzi słabnie. Należy używać deptrac lub phparkitect w CI, aby kompilacja kończyła się błędem, gdy Domain importuje Infrastructure. Reguła staje się gwarancją na etapie kompilacji, a nie nadzieją pokładaną w przeglądzie kodu.

# deptrac.yaml
deptrac:
  layers:
    - name: Domain
      collectors: [{ type: directory, value: src/.*/Domain/.* }]
    - name: Application
      collectors: [{ type: directory, value: src/.*/Application/.* }]
    - name: Infrastructure
      collectors: [{ type: directory, value: src/.*/Infrastructure/.* }]
  ruleset:
    Domain: []                       # Domain may depend on nothing
    Application: [Domain]
    Infrastructure: [Application, Domain]

Przekraczanie granic za pomocą DTO

Aby encje nie wyciekały na zewnątrz, dane przekraczające granicę są przesyłane jako prosty DTO, a nie jako encja lub model ORM. Przypadek użycia zwraca płaską strukturę, którą adapter może zserializować, dzięki czemu obiekt domenowy nigdy nie opuszcza rdzenia, a warstwa zewnętrzna nie uzyskuje dostępu do wewnętrznego stanu.

<?php
final class OrderSummary // boundary DTO, no behavior, no domain types
{
    public function __construct(
        public readonly string $orderId,
        public readonly int $totalCents,
    ) {}
}

final class PlaceOrderV2 {
    public function __construct(private OrderRepository $orders) {}
    public function execute(array $lineCents, bool $vip): OrderSummary {
        $order = new Order($lineCents, $vip);
        $this->orders->save($order);
        return new OrderSummary('ord_1', $order->total()->cents);
    }
}

Szybki test

Jaki kierunek zależności jest dozwolony zgodnie z Regułą zależności?

Podsumowanie

Przeszli Państwo od sprzężonego stosu warstwowego do Clean Architecture:

  • Reguła zależności: zależności w kodzie źródłowym wskazują wyłącznie do wewnątrz.
  • Encje zawierają reguły biznesowe organizacji w PHP niezależnym od frameworka.
  • Przypadki użycia orkiestrują działanie za pośrednictwem interfejsów portów, zwracając DTO, a nie HTTP.
  • Kontrolery i repozytoria ORM to zewnętrzne adaptery, które zależą od środka (DIP).
  • Struktura powinna wyrażać domenę, a narzędzia takie jak deptrac wymuszają tę regułę w CI.

Często zadawane pytania

Czy lekcja „Od architektury warstwowej do Clean Architecture” jest bezpłatna?

Tak — pełny tekst „Od architektury warstwowej do Clean Architecture” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Od architektury warstwowej do Clean Architecture”?

Proszę zrozumieć, dlaczego zależności powinny być skierowane do wnętrza. Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć PHP Academy?

Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Od architektury warstwowej do Clean Architecture”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?

Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Od architektury warstwowej do Clean Architecture
  2. Wyjaśnienie portów i adapterów
  3. Przypadki użycia i usługi aplikacyjne
  4. Odwrócenie zależności w praktyce
← Powrót do PHP Academy