PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Adapter and Facade in PHP – structural patterns

by Henryk Tews / Tuesday, 31 May 2022 / Published in Wzorce projektowe

Adapter and Facade are two structural patterns that manage complexity at the boundaries of a system. Adapter makes incompatible interfaces work together. Facade simplifies a complex subsystem behind a clean, high-level interface. Both appear constantly in real PHP projects – I show implementations and concrete examples from Magento 2.

Adapter – bridging incompatible interfaces

<?php

declare(strict_types=1);

// Your application expects this interface
interface PaymentGatewayInterface
{
    public function charge(int $amountInCents, string $currency, string $token): string;
    public function refund(string $transactionId, int $amountInCents): bool;
}

// Third-party SDK with a completely different interface
class StripeClient
{
    public function createPaymentIntent(array $params): object { /* Stripe API */ return new \stdClass(); }
    public function createRefund(array $params): object        { /* Stripe API */ return new \stdClass(); }
}

class PaypalClient
{
    public function executePayment(string $token, float $amount, string $currency): array { return []; }
    public function issueRefund(string $saleId, float $amount): array { return []; }
}

// Adapter wraps Stripe and exposes your interface
class StripeAdapter implements PaymentGatewayInterface
{
    public function __construct(private StripeClient $stripe) {}

    public function charge(int $amountInCents, string $currency, string $token): string
    {
        $intent = $this->stripe->createPaymentIntent([
            'amount'               => $amountInCents,
            'currency'             => strtolower($currency),
            'payment_method'       => $token,
            'confirm'              => true,
        ]);
        return $intent->id;
    }

    public function refund(string $transactionId, int $amountInCents): bool
    {
        $refund = $this->stripe->createRefund([
            'payment_intent' => $transactionId,
            'amount'         => $amountInCents,
        ]);
        return $refund->status === 'succeeded';
    }
}

// Adapter wraps PayPal and exposes the same interface
class PayPalAdapter implements PaymentGatewayInterface
{
    public function __construct(private PaypalClient $paypal) {}

    public function charge(int $amountInCents, string $currency, string $token): string
    {
        $result = $this->paypal->executePayment(
            $token,
            $amountInCents / 100,
            $currency
        );
        return $result['sale_id'] ?? '';
    }

    public function refund(string $transactionId, int $amountInCents): bool
    {
        $result = $this->paypal->issueRefund($transactionId, $amountInCents / 100);
        return ($result['state'] ?? '') === 'completed';
    }
}

// Service uses the interface - completely unaware of Stripe or PayPal specifics
class CheckoutService
{
    public function __construct(private PaymentGatewayInterface $gateway) {}

    public function pay(int $amount, string $currency, string $token): string
    {
        return $this->gateway->charge($amount, $currency, $token);
    }
}

// Swap payment provider by changing the adapter
$service = new CheckoutService(new StripeAdapter(new StripeClient()));

Adapter in Magento 2 – logging

<?php

// Magento wraps Monolog through an Adapter
// Your code uses Psr\Log\LoggerInterface
// Internally Magento binds it to Magento\Framework\Logger\Monolog
// which adapts Monolog to the PSR-3 interface

class MyService
{
    public function __construct(
        private \Psr\Log\LoggerInterface $logger // interface, not Monolog
    ) {}

    public function doWork(): void
    {
        $this->logger->info('Work done');
        // Works whether the logger is Monolog, a test mock, or any PSR-3 logger
    }
}

Facade – simplified interface to a subsystem

<?php

declare(strict_types=1);

// Complex subsystem - many classes with interrelated responsibilities
class ProductLoader     { public function load(int $id): array { return []; } }
class PriceCalculator   { public function calculate(array $product, int $customerId): float { return 0.0; } }
class StockChecker      { public function isAvailable(string $sku): bool { return true; } }
class ImageResizer      { public function resize(string $path, int $w, int $h): string { return ''; } }
class BreadcrumbBuilder { public function build(array $categories): array { return []; } }

// Facade provides a simple, task-oriented interface
class ProductPageFacade
{
    public function __construct(
        private ProductLoader $loader,
        private PriceCalculator $priceCalc,
        private StockChecker $stock,
        private ImageResizer $images,
        private BreadcrumbBuilder $breadcrumbs
    ) {}

    // One method does everything the product page needs
    public function getProductPageData(int $productId, int $customerId): array
    {
        $product = $this->loader->load($productId);

        return [
            'product'     => $product,
            'price'       => $this->priceCalc->calculate($product, $customerId),
            'in_stock'    => $this->stock->isAvailable($product['sku']),
            'image_url'   => $this->images->resize($product['image'], 800, 800),
            'breadcrumbs' => $this->breadcrumbs->build($product['category_ids']),
        ];
    }
}

// Controller becomes trivially simple
class ProductController
{
    public function show(int $id, int $customerId): array
    {
        return $this->facade->getProductPageData($id, $customerId);
    }
}

Adapter vs Facade – key difference

Aspect Adapter Facade
Problem solved Incompatible interfaces Overly complex subsystem
Interface count Two existing interfaces, one adapter Many classes, one facade
Changes existing code? No – wraps without modifying No – adds a layer above
Hides complexity? No – translates it Yes – simplifies it
Magento 2 example Monolog -> PSR-3 Logger Magento\Catalog\Helper\Data

Summary

Adapter is the go-to pattern when integrating third-party libraries – wrap them in an interface you own so you can swap implementations without changing application code. Facade is the go-to pattern when a subsystem has grown complex – one well-named method that does “what the caller needs” is worth more than exposing five internal services. Both patterns are about managing coupling at the right level.

About Henryk Tews

What you can read next

GoF patterns in Magento 2 – where to find them and how they work
Repository pattern – interface, implementation, SearchCriteria, testing with mock
State pattern – order state machine, serialisation, comparison with Strategy

© 2026 Created by

TOP
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Always active
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
Zobacz preferencje
  • {title}
  • {title}
  • {title}