Zasady SOLID w praktyce
Proszę zastosować pięć zasad SOLID w rzeczywistych klasach PHP.
Zasady SOLID w praktyce 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 SOLID
SOLID to pięć zasad projektowania, które sprawiają, że obiektowy PHP pozostaje elastyczny, testowalny i odporny na degradację. Nie są to reguły, których należy ślepo przestrzegać, lecz heurystyki zmniejszające sprzężenie i porządkujące odpowiedzialności. W tej lekcji zastosują Państwo każdą zasadę do konkretnych klas PHP, które rzeczywiście można wdrożyć.
- Single Responsibility
- Open/Closed
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
Zasada jednej odpowiedzialności
Klasa powinna mieć jeden powód do zmiany. Klasa, która tworzy raport, formatuje go jako HTML i wysyła pocztą elektroniczną, ma trzy powody do zmiany. Należy rozdzielić utrwalanie danych, formatowanie i transport.
Poniżej Invoice modeluje wyłącznie dane; renderowanie i zapisywanie znajdują się w innych miejscach.
<?php
final class Invoice {
public function __construct(
public readonly string $number,
public readonly int $cents
) {}
public function total(): float { return $this->cents / 100; }
}
final class InvoiceRenderer {
public function toText(Invoice $i): string {
return sprintf('Invoice %s: $%.2f', $i->number, $i->total());
}
}
$i = new Invoice('INV-1', 12500);
echo (new InvoiceRenderer())->toText($i), PHP_EOL;
SRP: rozpoznawanie problemu
Klasyczne naruszenie SRP to klasa, której nazwa zawiera słowo and, albo klasa Manager, która robi wszystko. Należy zwracać uwagę na metody zmieniające się z niezwiązanych ze sobą powodów biznesowych. Jeśli zmiana reguły podatkowej i zmiana układu PDF wymagają modyfikacji tej samej klasy, ma ona zbyt wiele odpowiedzialności.
Zasada otwarte-zamknięte
Elementy oprogramowania powinny być otwarte na rozszerzenia, ale zamknięte na modyfikacje. Dodanie nowego typu rabatu nie powinno wymagać edycji ogromnej instrukcji switch. Należy używać polimorfizmu: każda reguła powinna być osobną klasą implementującą wspólny interfejs.
<?php
interface Discount { public function apply(float $total): float; }
final class PercentOff implements Discount {
public function __construct(private float $pct) {}
public function apply(float $t): float { return $t * (1 - $this->pct / 100); }
}
final class FlatOff implements Discount {
public function __construct(private float $amount) {}
public function apply(float $t): float { return max(0, $t - $this->amount); }
}
function checkout(float $total, Discount ...$discounts): float {
foreach ($discounts as $d) { $total = $d->apply($total); }
return $total;
}
echo checkout(100, new PercentOff(10), new FlatOff(5)), PHP_EOL; // 85
Podstawienie Liskov
Typów pochodnych należy używać wszędzie tam, gdzie oczekiwany jest ich typ bazowy, bez zaskakiwania wywołującego. Słynny przykład: Square extends Rectangle łamie zasadę LSP, ponieważ ustawienie szerokości nie powinno po cichu zmieniać wysokości. Lepiej modelować je jako osobne typy za wspólnym interfejsem Shape.
<?php
interface Shape { public function area(): float; }
final class Rectangle implements Shape {
public function __construct(private float $w, private float $h) {}
public function area(): float { return $this->w * $this->h; }
}
final class Square implements Shape {
public function __construct(private float $side) {}
public function area(): float { return $this->side ** 2; }
}
$shapes = [new Rectangle(2, 3), new Square(4)];
foreach ($shapes as $s) { echo $s->area(), PHP_EOL; }
LSP a kontrakty
LSP ogranicza również kontrakty metod. Typ pochodny może osłabiać warunki wstępne i wzmacniać warunki końcowe, ale nigdy odwrotnie. Rzucanie nowym typem wyjątku, którego typ bazowy nie deklarował, albo zwracanie null tam, gdzie typ bazowy gwarantuje obiekt, narusza zasadę podstawienia. Kowariantne typy zwracane i kontrawariantne typy parametrów w PHP wymuszają część tych zasad na poziomie typów.
Segregacja interfejsów
Klienci nie powinni zależeć od metod, których nie używają. Rozbudowany interfejs Worker z metodami work() i eat() zmusza RobotWorker do utworzenia zaślepki dla eat(). Należy podzielić go na skoncentrowane na rolach interfejsy, aby każdy implementujący je typ zobowiązywał się tylko do tego, co rzeczywiście robi.
<?php
interface Workable { public function work(): string; }
interface Feedable { public function eat(): string; }
final class Human implements Workable, Feedable {
public function work(): string { return 'coding'; }
public function eat(): string { return 'lunch'; }
}
final class Robot implements Workable {
public function work(): string { return 'welding'; }
}
foreach ([new Human(), new Robot()] as $w) { echo $w->work(), PHP_EOL; }
Odwrócenie zależności
Polityka wysokiego poziomu powinna zależeć od abstrakcji, a nie od konkretnych szczegółów. Należy wstrzykiwać interfejs, a nie zakodowaną na stałe klasę. Dzięki temu można zastąpić rzeczywisty moduł pocztowy atrapą w testach, a w środowisku produkcyjnym użyć innego dostawcy bez modyfikowania kodu użytkownika.
<?php
interface Mailer { public function send(string $to, string $body): void; }
final class SmtpMailer implements Mailer {
public function send(string $to, string $body): void {
echo "SMTP -> $to: $body" . PHP_EOL;
}
}
final class SignupService {
public function __construct(private Mailer $mailer) {}
public function register(string $email): void {
$this->mailer->send($email, 'Welcome!');
}
}
(new SignupService(new SmtpMailer()))->register('a@b.com');
DIP a kontener
Odwrócenie zależności jest zasadą, a wstrzykiwanie zależności to jedna z technik jej realizacji. Kontener DI (PSR-11) łączy konkretną klasę SmtpMailer z abstrakcją Mailer w miejscu składania zależności. Kod korzystający z zależności nigdy nie wywołuje new SmtpMailer(), więc kierunek zależności w kodzie źródłowym wskazuje na abstrakcję, a nie na szczegół implementacyjny.
<?php
// Composition root wiring (pseudo-container)
$bindings = [
Mailer::class => fn() => new SmtpMailer(),
];
$resolve = fn(string $id) => $bindings[$id]();
$service = new SignupService($resolve(Mailer::class));
$service->register('user@example.com');
SOLID razem
Zasady wzajemnie się wzmacniają. ISP utrzymuje małe interfejsy, dzięki czemu wstrzykiwanie zależności zgodne z DIP pozostaje skoncentrowane; OCP opiera się na polimorfizmie, którego bezpieczeństwo zapewnia LSP; SRP dostarcza małych klas umożliwiających to wszystko. Należy dążyć do spójności wewnątrz i luźnego sprzężenia między elementami.
- Nie należy tworzyć nadmiernych abstrakcji dla klasy używanej tylko raz.
- Interfejs należy wprowadzić, gdy pojawi się druga implementacja lub obiekt testowy.
Pragmatyzm
SOLID jest środkiem, a nie celem. Przedwczesny interfejs z jedną implementacją dodaje warstwę pośrednią bez żadnej korzyści („speculative generality”). Zasady należy stosować, gdy zmiana rzeczywiście nadchodzi lub jest wyraźnie bliska. Refaktoryzacja w kierunku SOLID jest tania, gdy istnieją testy; ślepe dążenie do SOLID od pierwszego dnia jest stratą czasu.
Szybkie sprawdzenie
Jaką zasadę narusza problem dziedziczenia Square/Rectangle?
Powtórzenie
Zastosowali Państwo wszystkie pięć zasad SOLID w rzeczywistym kodzie PHP:
- SRP: jedna przyczyna zmiany na klasę.
- OCP: rozszerzanie za pomocą nowych klas polimorficznych zamiast edytowania instrukcji switch.
- LSP: typy pochodne respektują kontrakt typu bazowego.
- ISP: małe interfejsy ról zamiast rozbudowanych interfejsów.
- DIP: zależność od abstrakcji i wstrzykiwanie szczegółów w miejscu składania zależności.
Należy wykorzystywać je jako wskazówki podczas refaktoryzacji, a nie jako formalność.
Często zadawane pytania
Czy lekcja „Zasady SOLID w praktyce” jest bezpłatna?
Tak — pełny tekst „Zasady SOLID w praktyce” 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 „Zasady SOLID w praktyce”?
Proszę zastosować pięć zasad SOLID w rzeczywistych klasach PHP. Ć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 „Zasady SOLID w praktyce”?
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
- Zasady SOLID w praktyce
- Wzorce kreacyjne: Factory, Builder, Singleton
- Wzorce strukturalne: Adapter, Decorator, Facade
- Wzorce behawioralne: Strategy, Observer, Command