Från lagerindelad till ren arkitektur
Förstå varför beroenden bör peka inåt.
Från lagerindelad till ren arkitektur är en gratis lektion i PHP Academy på CoddyKit. Detta är lektion 1 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för PHP Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i PHP Academy innehåller totalt 4 lektioner.
Varför Clean Architecture
Ni känner redan till den klassiska PHP-stacken med tre lager: Controller → Service → Repository → Database. Den fungerar, men affärslogiken blir kopplad till Eloquent, Doctrine, HTTP-begäran och ramverkets livscykel. Clean Architecture vänder på beroenderiktningen, så att domänen inte känner till infrastrukturen. Resultatet blir testbara användningsfall, utbytbara adaptrar och en kodbas som överlever uppgraderingar av ramverket.
Dependency Rule
Den enda regeln i Clean Architecture är att beroenden i källkoden endast pekar inåt. Inre cirklar (entiteter, användningsfall) får aldrig referera till yttre cirklar (controllers, ORM:er, ramverk). Vid körning går styrningen utåt via gränssnitt, men vid kompilering/import importerar inget inre något yttre.
- Entiteter: företagsövergripande regler
- Användningsfall: applikationsregler
- Adaptrar: controllers, presenters, gateways
- Ramverk och drivrutiner: DB, HTTP, webben
Ett tätt kopplat lagerexempel
Här är den typ av tjänst som de flesta PHP-appar levereras med. Lägg märke till hur domänlogiken är sammanflätad med Eloquent och HTTP-svaret. Ni kan inte enhetstesta rabattregeln utan en databas och ett ramverk.
<?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]);
}
}Entiteter: ramverksfri domän
En entitet kodifierar företagsövergripande regler och är inte beroende av någonting. Ren PHP, inga annotationer och ingen basklass från ORM:et. Den kan konstrueras helt och hållet i ett test.
<?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; // 2700Användningsfall äger arbetsflödet
Ett användningsfall (interactor) orkestrerar entiteter och kommunicerar med omvärlden endast via gränssnitt (portar). Det tar emot en request-DTO och returnerar en response-DTO — aldrig ett HTTP-objekt.
<?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;
}
}Gränsen är ett gränssnitt
Användningsfallet deklarerar det OrderRepository-gränssnitt som det behöver. Gränssnittet ligger i den inre cirkeln; den konkreta Eloquent-/Doctrine-implementationen ligger utanför och är beroende inåt. Detta är principen om beroendeinversion tillämpad vid en arkitekturgräns.
Källkodens beroenderiktning: EloquentOrderRepository → OrderRepository (gränssnittet), aldrig tvärtom.
<?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]);
}
}Testning utan infrastruktur
Eftersom användningsfallet är beroende av ett gränssnitt kan tester injicera en fejk. Ingen databas och ingen uppstart av ramverket — enhetstester på mikrosekunder som verifierar rent affärsbeteende.
<?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;Controllers blir tunna adaptrar
Controllern är nu en adapter: den översätter HTTP till ett anrop av ett användningsfall och resultatet tillbaka till HTTP. Den innehåller inga affärsregler. Byt REST mot CLI eller en köarbetare, så förblir användningsfallet oförändrat.
<?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);
}
}Screaming Architecture
Mappstrukturen ska tydligt signalera domänen, inte ramverket. Undvik Controllers/ och Models/ på toppnivån. Organisera efter förmåga, så att en nykomling ser vad appen gör.
src/Ordering/Domain/— entiteter, värdeobjektsrc/Ordering/Application/— användningsfall, portgränssnittsrc/Ordering/Infrastructure/— Eloquent-repositories, HTTP-controllers
Varje avgränsad kontext är en mapp på toppnivå; ramverket finns vid kanterna.
Upprätthålla Dependency Rule
Utan verktyg urholkas disciplinen. Använd deptrac eller phparkitect i CI för att få byggprocessen att misslyckas när Domain importerar Infrastructure. Regeln blir en garanti vid kompilering i stället för en förhoppning vid kodgranskning.
# 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]Överskrida gränser med DTO:er
För att hindra entiteter från att läcka utåt färdas data som passerar en gräns i form av en enkel DTO, inte som en entitet eller en ORM-modell. Användningsfallet returnerar en platt struktur som adaptern kan serialisera, så att domänobjektet aldrig lämnar kärnan och det yttre lagret aldrig får en referens till det interna tillståndet.
<?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);
}
}Snabbkontroll
Vilken beroenderiktning är tillåten enligt Dependency Rule?
Sammanfattning
Ni gick från en kopplad lagerstack till Clean Architecture:
- Dependency Rule: källkodens beroenden pekar endast inåt.
- Entiteter innehåller företagsövergripande regler i ramverksfri PHP.
- Användningsfall orkestrerar via portgränssnitt och returnerar DTO:er, inte HTTP.
- Controllers och ORM-repositories är yttre adaptrar som är beroende inåt (DIP).
- Strukturen ska tydligt signalera domänen, och verktyg som deptrac upprätthåller regeln i CI.
Lär dig PHP med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 49
- Lektioner
- 195
Vanliga frågor
Är lektionen ”Från lagerindelad till ren arkitektur” gratis?
Ja – hela texten till ”Från lagerindelad till ren arkitektur” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i PHP Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i PHP Academy innehåller totalt 4 lektioner.
Vad lär jag mig i ”Från lagerindelad till ren arkitektur”?
Förstå varför beroenden bör peka inåt. Ni övar på PHP Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig PHP Academy?
Du behöver inga förkunskaper. Utbildningen i PHP Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 1 av 4.
Hur lång tid tar lektionen ”Från lagerindelad till ren arkitektur”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här PHP Academy-lektionen?
Ja. Varje PHP Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Från lagerindelad till ren arkitektur
- Ports and Adapters förklarat
- Use cases och applikationstjänster
- Beroendeinversion i praktiken