StructuralPHPverifiedVerified
Bridge Pattern in PHP
Decouples an abstraction from its implementation so that the two can vary independently.
How to Implement the Bridge Pattern in PHP
1Step 1: Define the implementation interface (the "bridge")
interface Renderer
{
public function renderCircle(float $x, float $y, float $radius): string;
public function renderSquare(float $x, float $y, float $side): string;
}2Step 2: Implement concrete implementations
class SvgRenderer implements Renderer
{
public function renderCircle(float $x, float $y, float $radius): string
{
return "<circle cx=\"{$x}\" cy=\"{$y}\" r=\"{$radius}\"/>";
}
public function renderSquare(float $x, float $y, float $side): string
{
return "<rect x=\"{$x}\" y=\"{$y}\" width=\"{$side}\" height=\"{$side}\"/>";
}
}
class CanvasRenderer implements Renderer
{
public function renderCircle(float $x, float $y, float $radius): string
{
return "ctx.arc({$x}, {$y}, {$radius}, 0, 2*PI)";
}
public function renderSquare(float $x, float $y, float $side): string
{
return "ctx.fillRect({$x}, {$y}, {$side}, {$side})";
}
}3Step 3: Define the abstraction that uses the implementation
abstract class Shape
{
public function __construct(protected Renderer $renderer) {}
abstract public function draw(): string;
}
class CircleShape extends Shape
{
public function __construct(
Renderer $renderer,
private readonly float $x,
private readonly float $y,
private readonly float $radius,
) {
parent::__construct($renderer);
}
public function draw(): string
{
return $this->renderer->renderCircle($this->x, $this->y, $this->radius);
}
}
// Usage — shape and renderer vary independently
$svg = new CircleShape(new SvgRenderer(), 10, 20, 5);
$canvas = new CircleShape(new CanvasRenderer(), 10, 20, 5);
echo $svg->draw(); // SVG output
echo $canvas->draw(); // Canvas output<?php
declare(strict_types=1);
// [step] Define the implementation interface for message delivery
interface MessageTransportInterface
{
public function send(string $recipient, string $subject, string $body): TransportResult;
public function getName(): string;
public function isAvailable(): bool;
}
final readonly class TransportResult
{
public function __construct(
public bool $success,
public string $messageId,
public ?string $error = null,
public float $durationMs = 0,
) {}
}
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
}
// [step] Implement transport implementations
final class SmtpTransport implements MessageTransportInterface
{
public function __construct(
private readonly string $host,
private readonly int $port,
private readonly string $username,
) {}
public function send(string $recipient, string $subject, string $body): TransportResult
{
$start = hrtime(true);
$messageId = uniqid('smtp_', true);
$durationMs = (hrtime(true) - $start) / 1e6;
return new TransportResult(true, $messageId, durationMs: $durationMs);
}
public function getName(): string { return 'smtp'; }
public function isAvailable(): bool { return true; }
}
final class SendGridTransport implements MessageTransportInterface
{
public function __construct(
private readonly string $apiKey,
) {}
public function send(string $recipient, string $subject, string $body): TransportResult
{
$messageId = uniqid('sg_', true);
return new TransportResult(true, $messageId);
}
public function getName(): string { return 'sendgrid'; }
public function isAvailable(): bool { return $this->apiKey !== ''; }
}
// [step] Define the abstraction hierarchy
abstract class Notification
{
public function __construct(
protected readonly MessageTransportInterface $transport,
protected readonly LoggerInterface $logger,
) {}
abstract public function send(string $recipient): TransportResult;
protected function doSend(string $recipient, string $subject, string $body): TransportResult
{
if (!$this->transport->isAvailable()) {
$this->logger->error("Transport unavailable", ['transport' => $this->transport->getName()]);
return new TransportResult(false, '', error: 'Transport unavailable');
}
$result = $this->transport->send($recipient, $subject, $body);
$this->logger->info("Notification sent", [
'transport' => $this->transport->getName(),
'success' => $result->success,
]);
return $result;
}
}
// [step] Refined abstractions — different notification types
final class WelcomeNotification extends Notification
{
public function __construct(
MessageTransportInterface $transport,
LoggerInterface $logger,
private readonly string $appName,
) {
parent::__construct($transport, $logger);
}
public function send(string $recipient): TransportResult
{
return $this->doSend(
$recipient,
"Welcome to {$this->appName}!",
"Thanks for joining {$this->appName}. We're glad to have you!",
);
}
}
final class PasswordResetNotification extends Notification
{
public function __construct(
MessageTransportInterface $transport,
LoggerInterface $logger,
private readonly string $resetUrl,
) {
parent::__construct($transport, $logger);
}
public function send(string $recipient): TransportResult
{
return $this->doSend(
$recipient,
'Password Reset Request',
"Click here to reset your password: {$this->resetUrl}",
);
}
}Bridge Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Bridge Pattern in the Real World
“A TV remote (abstraction) works with any brand of television (implementation) because both sides communicate through an agreed IR protocol (the bridge). Samsung and LG can redesign their TVs, and universal remote makers can add new button layouts—neither side needs to know about the other’s internal design, only the shared protocol.”