StructuralPHPverifiedVerified
Adapter Pattern in PHP
Converts the interface of a class into another interface that clients expect, allowing incompatible interfaces to work together.
How to Implement the Adapter Pattern in PHP
1Step 1: Define the target interface the client expects
interface MediaPlayer
{
public function play(string $filename): string;
}2Step 2: Define the incompatible adaptee (legacy)
class LegacyAudioDecoder
{
public function decodeOgg(string $file): string
{
return "Decoded OGG: {$file}";
}
public function decodeMp3(string $file): string
{
return "Decoded MP3: {$file}";
}
}3Step 3: Create the adapter that bridges the interfaces
class AudioPlayerAdapter implements MediaPlayer
{
public function __construct(
private readonly LegacyAudioDecoder $decoder,
) {}
public function play(string $filename): string
{
$ext = pathinfo($filename, PATHINFO_EXTENSION);
return match ($ext) {
'ogg' => $this->decoder->decodeOgg($filename),
'mp3' => $this->decoder->decodeMp3($filename),
default => throw new \InvalidArgumentException("Unsupported format: {$ext}"),
};
}
}
// Usage
$player = new AudioPlayerAdapter(new LegacyAudioDecoder());
echo $player->play('song.mp3'); // "Decoded MP3: song.mp3"<?php
declare(strict_types=1);
// [step] Define the unified logger interface (target)
interface LoggerInterface
{
public function log(string $level, string $message, array $context = []): void;
public function info(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
public function warning(string $message, array $context = []): void;
}
// [step] Third-party logger APIs (adaptees)
class MonologLogger
{
public function addRecord(int $level, string $message, array $context = []): void
{
// Monolog's native API
}
}
class SentryClient
{
public function captureMessage(string $message, string $level, array $extra = []): string
{
return uniqid('sentry_');
}
}
class SlackWebhook
{
public function post(string $webhookUrl, array $payload): bool
{
return true;
}
}
// [step] Implement adapters for each third-party service
final class MonologAdapter implements LoggerInterface
{
private const LEVEL_MAP = [
'debug' => 100, 'info' => 200, 'warning' => 300,
'error' => 400, 'critical' => 500,
];
public function __construct(private readonly MonologLogger $monolog) {}
public function log(string $level, string $message, array $context = []): void
{
$numericLevel = self::LEVEL_MAP[$level] ?? 200;
$this->monolog->addRecord($numericLevel, $message, $context);
}
public function info(string $message, array $context = []): void
{
$this->log('info', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('error', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('warning', $message, $context);
}
}
final class SentryAdapter implements LoggerInterface
{
public function __construct(
private readonly SentryClient $sentry,
private readonly string $minLevel = 'error',
) {}
public function log(string $level, string $message, array $context = []): void
{
$levels = ['debug' => 0, 'info' => 1, 'warning' => 2, 'error' => 3, 'critical' => 4];
if (($levels[$level] ?? 0) >= ($levels[$this->minLevel] ?? 0)) {
$this->sentry->captureMessage($message, $level, $context);
}
}
public function info(string $message, array $context = []): void { $this->log('info', $message, $context); }
public function error(string $message, array $context = []): void { $this->log('error', $message, $context); }
public function warning(string $message, array $context = []): void { $this->log('warning', $message, $context); }
}
// [step] Implement a composite adapter that broadcasts to multiple loggers
final class CompositeLoggerAdapter implements LoggerInterface
{
/** @param LoggerInterface[] $loggers */
public function __construct(
private readonly array $loggers,
) {}
public function log(string $level, string $message, array $context = []): void
{
foreach ($this->loggers as $logger) {
try {
$logger->log($level, $message, $context);
} catch (\Throwable) {
// Fail silently to prevent logging from breaking the application
}
}
}
public function info(string $message, array $context = []): void { $this->log('info', $message, $context); }
public function error(string $message, array $context = []): void { $this->log('error', $message, $context); }
public function warning(string $message, array $context = []): void { $this->log('warning', $message, $context); }
}Adapter Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Adapter Pattern in the Real World
“A travel power adapter lets your American laptop plug (client) work in a European wall socket (adaptee) without modifying either. The adapter speaks both “languages”, translating the two-pin plug to the two-round-pin socket, making them interoperable without any changes on either side.”