~/programowanie cat fasada-wzorzec-projektowy-php.md
Wzorzec projektowy Fasada w PHP – przykład krok po kroku i różnice wobec fasad w Laravelu
Wzorzec projektowy Fasada w PHP: czym jest, kiedy go stosować i jak go napisać w PHP 8 z wstrzykiwaniem zależności. Plus czym różnią się od niego fasady Laravela.

~ xad tldr fasada-wzorzec-projektowy-…
- Fasada to strukturalny wzorzec projektowy: jedna klasa udostępnia prosty interfejs do kilku współpracujących klas (podsystemu) i ukrywa kolejność ich wywołań.
- Fasada nie dodaje logiki biznesowej ani nie blokuje dostępu do podsystemu — kto potrzebuje szczegółów, nadal może użyć klas bezpośrednio.
- W nowoczesnym PHP zależności fasady wstrzykuj przez konstruktor, a nie twórz ich operatorem new — dzięki temu klasa jest testowalna.
- Fasady w Laravelu (Cache::get, DB::table) to co innego niż wzorzec GoF: to statyczne proxy do obiektów z kontenera usług.
- Największe ryzyko to rozrost fasady w „boską klasę” — dziel ją według przypadków użycia.
$ tree --spis-tresci
Fasada (ang. Facade) to strukturalny wzorzec projektowy, w którym jedna klasa udostępnia prosty interfejs do złożonego zestawu klas — podsystemu. W PHP fasada przyjmuje w konstruktorze potrzebne obiekty, a w swoich metodach wywołuje je we właściwej kolejności, dzięki czemu kod kontrolera czy komendy CLI ogranicza się do jednego wywołania, np. $checkout->placeOrder($cart, $customer).
Wzorzec pochodzi z klasycznej książki „Gang of Four” (GoF) z 1994 roku, ale w PHP jest wciąż bardzo praktyczny: przy integracji z zewnętrznymi bibliotekami, API płatności czy przy porządkowaniu starego kodu. Poniżej znajdziesz przykład w PHP 8, typowe błędy oraz wyjaśnienie, dlaczego „fasady” w Laravelu to zupełnie inna konstrukcja.
Jaki problem rozwiązuje wzorzec Fasada
Wyobraź sobie złożenie zamówienia w sklepie. Kontroler musi:
- sprawdzić i zarezerwować stany magazynowe,
- obciążyć kartę klienta przez bramkę płatności,
- utworzyć przesyłkę u kuriera,
- wysłać e-mail z potwierdzeniem.
Jeśli każdy kontroler, endpoint API i komenda konsolowa wykonuje te kroki samodzielnie, ta sama sekwencja jest skopiowana w kilku miejscach. Każdy z nich zna szczegóły czterech klas, a zmiana dostawcy płatności oznacza poprawki w wielu plikach.
Fasada zbiera tę sekwencję w jednym miejscu. Klient (kod, który z niej korzysta) zna tylko fasadę. Podsystem nadal istnieje i można go użyć bezpośrednio, gdy potrzebna jest pełna kontrola — fasada niczego nie zabrania, jedynie upraszcza typowy scenariusz.
Struktura wzorca: klient, fasada, podsystem
| Element | Rola | W przykładzie |
|---|---|---|
| Klient | Korzysta z prostego interfejsu | Kontroler, komenda CLI |
| Fasada | Koordynuje podsystem, ustala kolejność wywołań | CheckoutFacade |
| Klasy podsystemu | Wykonują właściwą pracę, nie wiedzą o fasadzie | Inventory, PaymentGateway, ShippingService, Mailer |
Dwie zasady odróżniają porządną fasadę od przypadkowej klasy pomocniczej:
- Podsystem nie zależy od fasady. Klasy
InventoryczyMailerdziałają tak samo z fasadą i bez niej. - Fasada nie zawiera reguł biznesowych. Deleguje pracę. Jeśli zaczyna liczyć rabaty i walidować dane, to sygnał, że ta logika powinna trafić do osobnej klasy domenowej.
Przykład: fasada procesu zamówienia w PHP 8
Najpierw podsystem. W prawdziwej aplikacji te klasy miałyby więcej metod i zależności (klienta HTTP, repozytoria), tu skracam je do istoty.
<?php
declare(strict_types=1);
final class Inventory
{
public function reserve(array $items): string
{
// sprawdzenie stanów i rezerwacja, zwraca ID rezerwacji
return 'RES-' . bin2hex(random_bytes(4));
}
public function release(string $reservationId): void
{
// zwolnienie rezerwacji
}
}
final class PaymentGateway
{
public function charge(string $customerToken, int $amountInGrosze): string
{
// wywołanie API operatora płatności, zwraca ID transakcji
return 'TX-' . bin2hex(random_bytes(4));
}
}
final class ShippingService
{
public function createShipment(string $address, array $items): string
{
return 'PKG-' . bin2hex(random_bytes(4));
}
}
final class Mailer
{
public function sendOrderConfirmation(string $email, string $orderId): void
{
// wysyłka e-maila
}
}
Teraz fasada (kod wymaga PHP 8.2 lub nowszego ze względu na klasę readonly). Zależności przychodzą przez konstruktor (od PHP 8.0 można zadeklarować je od razu jako właściwości — tzw. constructor property promotion), a metoda placeOrder() ukrywa kolejność kroków i obsługę błędów.
<?php
declare(strict_types=1);
final readonly class OrderResult
{
public function __construct(
public string $orderId,
public string $transactionId,
public string $trackingNumber,
) {}
}
final class CheckoutFacade
{
public function __construct(
private readonly Inventory $inventory,
private readonly PaymentGateway $payments,
private readonly ShippingService $shipping,
private readonly Mailer $mailer,
) {}
public function placeOrder(Cart $cart, Customer $customer): OrderResult
{
$reservationId = $this->inventory->reserve($cart->items());
try {
$transactionId = $this->payments->charge(
$customer->paymentToken(),
$cart->totalInGrosze(),
);
} catch (PaymentFailed $e) {
$this->inventory->release($reservationId);
throw $e;
}
$trackingNumber = $this->shipping->createShipment(
$customer->address(),
$cart->items(),
);
$orderId = 'ORD-' . $reservationId;
$this->mailer->sendOrderConfirmation($customer->email(), $orderId);
return new OrderResult($orderId, $transactionId, $trackingNumber);
}
}
Klasy Cart, Customer i wyjątek PaymentFailed pomijam — to zwykłe obiekty domenowe. Kod klienta jest teraz krótki i czytelny:
<?php
// np. w kontrolerze; fasadę dostarcza kontener DI frameworka
$result = $checkout->placeOrder($cart, $customer);
echo "Zamówienie {$result->orderId}, numer przesyłki: {$result->trackingNumber}";
Zwróć uwagę na kompensację w bloku catch: jeśli płatność się nie powiedzie, fasada zwalnia rezerwację. Gdyby każdy kontroler sam koordynował podsystem, o tym kroku łatwo byłoby zapomnieć w jednym z miejsc.
Dlaczego nie tworzyć zależności przez new w fasadzie
W wielu starszych poradnikach fasada wygląda tak:
public function __construct()
{
$this->inventory = new Inventory();
$this->payments = new PaymentGateway();
}
To działa, ale ma dwie wady. Po pierwsze, fasady nie da się przetestować bez prawdziwej bramki płatności — nie podmienisz PaymentGateway na atrapę. Po drugie, fasada musi wiedzieć, jak skonfigurować każdą klasę (klucze API, adresy, klienta HTTP), co łamie zasadę pojedynczej odpowiedzialności.
Przy wstrzykiwaniu przez konstruktor test w PHPUnit jest prosty:
<?php
use PHPUnit\Framework\TestCase;
final class CheckoutFacadeTest extends TestCase
{
public function testReleasesReservationWhenPaymentFails(): void
{
$inventory = $this->createMock(Inventory::class);
$inventory->method('reserve')->willReturn('RES-1');
$inventory->expects($this->once())->method('release')->with('RES-1');
$payments = $this->createMock(PaymentGateway::class);
$payments->method('charge')->willThrowException(new PaymentFailed());
$facade = new CheckoutFacade(
$inventory,
$payments,
$this->createMock(ShippingService::class),
$this->createMock(Mailer::class),
);
$this->expectException(PaymentFailed::class);
$facade->placeOrder($this->cart(), $this->customer());
}
}
Uwaga praktyczna: PHPUnit nie zamockuje klasy oznaczonej jako final. W prawdziwym projekcie wprowadź interfejsy (np. PaymentGatewayInterface) i to od nich uzależnij fasadę. To zresztą dobra praktyka niezależnie od testów — łatwiej wymienisz dostawcę płatności. Taką samą rolę pełni warstwa middleware w aplikacjach webowych: oddziela kod aplikacji od szczegółów infrastruktury.
Fasady w Laravelu to nie wzorzec GoF
Jeśli szukasz informacji o fasadach w PHP, szybko trafisz na Laravela: Cache::get('klucz'), DB::table('users'), Route::get(...). To nazwa myląca, bo mechanizm jest inny.
Fasada Laravela to klasa dziedzicząca po Illuminate\Support\Facades\Facade, która implementuje metodę getFacadeAccessor(). Wywołanie statycznej metody trafia do magicznej metody __callStatic(), ta pobiera obiekt z kontenera usług i wywołuje na nim właściwą metodę:
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Checkout extends Facade
{
protected static function getFacadeAccessor(): string
{
return \App\Services\CheckoutFacade::class;
}
}
// użycie: Checkout::placeOrder($cart, $customer);
| Wzorzec Fasada (GoF) | Fasada w Laravelu | |
|---|---|---|
| Cel | Uprościć interfejs do wielu klas | Wygodny, statyczny dostęp do usługi z kontenera |
| Liczba obiektów za fasadą | Zwykle kilka | Jeden |
| Wywołanie | Metoda instancji | Metoda statyczna przekierowana do instancji |
| Testowanie | Atrapy wstrzykiwane w konstruktorze | Checkout::shouldReceive(...) (Mockery), dla części wbudowanych fasad np. Mail::fake() |
Obie rzeczy da się połączyć: piszesz klasyczną fasadę (jak CheckoutFacade wyżej), rejestrujesz ją w kontenerze, a fasada Laravela daje do niej skrót. W większości przypadków wystarczy jednak zwykłe wstrzyknięcie przez konstruktor kontrolera, które jest bardziej jawne.
Fasada a Adapter, Mediator i Proxy
Fasadę łatwo pomylić z innymi wzorcami strukturalnymi i behawioralnymi:
- Adapter — zmienia interfejs jednej klasy na taki, jakiego oczekuje klient (np. owija SDK starej bramki płatności w Twój
PaymentGatewayInterface). Fasada nie dopasowuje interfejsu, tylko tworzy nowy, prostszy. - Mediator — pośredniczy w komunikacji między obiektami, które o sobie wiedzą przez mediatora. W fasadzie klasy podsystemu nie wiedzą o fasadzie i nie komunikują się przez nią nawzajem.
- Proxy — ma ten sam interfejs co obiekt, który zastępuje (np. leniwe ładowanie, cache, kontrola dostępu). Fasada ma inny, uproszczony interfejs.
- Dekorator — dokłada zachowanie do pojedynczego obiektu bez zmiany interfejsu.
W praktyce wzorce się łączą: fasada często korzysta z adapterów do zewnętrznych bibliotek. Przegląd pozostałych wzorców (z przykładami w Javie, ale idee są te same) znajdziesz w tekście o wzorcach projektowych Java.
Kiedy stosować fasadę, a kiedy jej unikać
Fasada ma sens, gdy:
- ta sama sekwencja wywołań kilku klas powtarza się w wielu miejscach (kontrolery, komendy, kolejki),
- integrujesz złożoną bibliotekę lub zewnętrzne API, z którego aplikacja potrzebuje tylko kilku operacji — np. klienta REST API z autoryzacją, ponawianiem żądań i mapowaniem odpowiedzi,
- refaktoryzujesz stary kod i chcesz odgrodzić nowe moduły od chaosu w starych (fasada jako granica warstwy),
- budujesz moduł, który inne zespoły mają używać bez znajomości jego wnętrza.
Unikaj jej, gdy:
- podsystem to jedna prosta klasa — fasada dodałaby tylko warstwę przekierowań,
- fasada zaczyna mieć dziesiątki metod obsługujących niezwiązane przypadki — robi się z niej „boska klasa” (God Object). Zamiast jednej
ShopFacadeutwórzCheckoutFacade,ReturnsFacade,ReportingFacade, - klienci potrzebują różnych, szczegółowych wariantów działania podsystemu — wtedy próba upchnięcia wszystkiego w parametrach fasady daje metody z ośmioma flagami logicznymi.
Dobra fasada ma krótkie metody nazwane od przypadków użycia („złóż zamówienie”, „zwróć produkt”), a nie od technicznych kroków. Jeśli po przeczytaniu jej publicznych metod rozumiesz, co moduł robi dla reszty aplikacji, wzorzec spełnia swoje zadanie.
~ man faq
Najczęściej zadawane pytania
Czym jest wzorzec Fasada w PHP?
To klasa, która udostępnia prosty zestaw metod do złożonego podsystemu, np. do obsługi zamówienia, która pod spodem korzysta z magazynu, płatności, wysyłki i powiadomień. Kod klienta wywołuje jedną metodę zamiast koordynować kilka klas.
Czym różni się Fasada od Adaptera?
Adapter dopasowuje istniejący interfejs jednej klasy do interfejsu oczekiwanego przez klienta. Fasada definiuje nowy, prostszy interfejs do wielu klas naraz. Adapter tłumaczy, fasada upraszcza.
Czy fasady w Laravelu to wzorzec Fasada?
Nie w klasycznym znaczeniu. Fasada Laravela to klasa ze statycznymi metodami, która przekazuje wywołania do obiektu pobranego z kontenera usług. Nazwa jest myląca, bo działa raczej jak statyczny proxy lub service locator.
Kiedy nie stosować wzorca Fasada?
Gdy podsystem składa się z jednej czy dwóch prostych klas albo gdy każdy klient potrzebuje innej, pełnej kontroli nad podsystemem. Wtedy fasada jest zbędną warstwą pośrednią.
Ten artykuł jest częścią tematu
$ whoami
Założyciel i redaktor XAD.pl. Pisze o sieciach, bezpieczeństwie IT, administracji systemami Windows i Linux oraz o sprzęcie, który sprawia ludziom problemy na co dzień.


