PHP Adapter Design Pattern Структурный паттерн

Паттерн Адаптер в PHP

Короткий скринкаст: паттерн «Адаптер» (Adapter) — приводим чуждое API к единому контракту, не трогая легаси-код.

Adapter Pattern — открыть страницу урока

Описание

У клиента есть контракт PaymentInterface::process(). Старый шлюз LegacyPaymentGateway не совместим с ним: у него charge() и возвращает bool. Адаптер PaymentAdapter реализует контракт, держит внутри легаси-шлюз и переводит его вызовы в ожидаемый ответ. Легаси-код не меняется.

UML-диаграмма

classDiagram class PaymentInterface { <<interface>> +process(amount: float) string } class LegacyPaymentGateway { +charge(amount: float) bool } class PaymentAdapter { -gateway LegacyPaymentGateway +process(amount: float) string } PaymentAdapter ..|> PaymentInterface : implements PaymentAdapter o-- LegacyPaymentGateway : wraps

Код

PaymentInterface.php

interface PaymentInterface
{
    public function process(float $amount): string;
}

LegacyPaymentGateway.php

class LegacyPaymentGateway
{
    public function charge(float $amount): bool
    {
        return $amount > 0;
    }
}

PaymentAdapter.php

class PaymentAdapter implements PaymentInterface
{
    public function __construct(
        private LegacyPaymentGateway $gateway
    ) {}

    public function process(float $amount): string
    {
        $ok = $this->gateway->charge($amount);

        return $ok ? "paid {$amount}" : 'declined';
    }
}

demo.php

require_once 'PaymentInterface.php';
require_once 'LegacyPaymentGateway.php';
require_once 'PaymentAdapter.php';

function checkout(PaymentInterface $payment, float $amount): void
{
    echo $payment->process($amount) . '!';
}

$gateway = new LegacyPaymentGateway();
$payment = new PaymentAdapter($gateway);

checkout($payment, 99.5);
echo PHP_EOL;
checkout($payment, 0);

Заметка: что даёт адаптер

Адаптер — это единственный «шов», через который легаси или сторонний SDK работает с нашим кодом. Клиент зависят от интерфейса, а не от конкретной реализации: заменить шлюз или добавить новую реализацию легко. Главный принцип — чужой код не правим руками, его оборачиваем.

Пример результата

После php demo.php в терминале появится:

paid 99.5!
declined!

Преимущества

  • Позволяет переиспользовать несовместимый код без его правок.
  • Изолирует клиента от сторонней библиотеки или legacy-API.
  • Всё преобразование собрано в одном месте — в адаптере.
  • Позволяет подменять одну реализацию другой за общим интерфейсом.

Когда использовать

  • Когда нужно подключить стороннюю или legacy-библиотеку с чужим интерфейсом.
  • Когда интерфейсы клиента и подсистемы не совпадают.
  • Когда важно изолировать внешнюю зависимость в одном классе.
  • Когда нужно несколько реализаций под одним общим интерфейсом.

Использование в Laravel

В Laravel Адаптер — это Illuminate\Filesystem\FilesystemAdapter: он приводит разные драйверы Flysystem (local, s3, ftp) к единому интерфейсу Illuminate\Contracts\Filesystem\Filesystem с методами put(), get(), delete(). Так же устроены адаптеры кэша (Illuminate\Cache\RedisStore), почтовые транспорты и коннекторы очередей: внешняя библиотека спрятана за контрактом Laravel.