CreationalPHPverifiedVerified
Factory Method Pattern in PHP
Defines an interface for creating an object but lets subclasses decide which class to instantiate. Defers instantiation to subclasses.
How to Implement the Factory Method Pattern in PHP
1Step 1: Define the product interface and concrete products
interface Transport
{
public function deliver(string $cargo): string;
}
class Truck implements Transport
{
public function deliver(string $cargo): string
{
return "Delivering '{$cargo}' by truck on road";
}
}
class Ship implements Transport
{
public function deliver(string $cargo): string
{
return "Delivering '{$cargo}' by ship across ocean";
}
}2Step 2: Define the creator with the factory method
abstract class Logistics
{
// Factory method — subclasses decide which Transport to create
abstract protected function createTransport(): Transport;
// Business logic uses the product interface
public function planDelivery(string $cargo): string
{
$transport = $this->createTransport();
return $transport->deliver($cargo);
}
}3Step 3: Concrete creators override the factory method
class RoadLogistics extends Logistics
{
protected function createTransport(): Transport
{
return new Truck();
}
}
class SeaLogistics extends Logistics
{
protected function createTransport(): Transport
{
return new Ship();
}
}4Step 4: Client code works with the creator abstraction
$logistics = new RoadLogistics();
echo $logistics->planDelivery('Electronics'); // Delivering 'Electronics' by truck on road<?php
declare(strict_types=1);
// [step] Define notification interfaces and value objects
interface NotificationInterface
{
public function send(string $recipient, string $message): NotificationResult;
public function getChannel(): string;
}
final readonly class NotificationResult
{
public function __construct(
public bool $success,
public string $messageId,
public string $channel,
public ?string $error = null,
) {}
}
interface LoggerInterface
{
public function info(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
}
// [step] Implement concrete notification channels
final class EmailNotification implements NotificationInterface
{
public function __construct(
private readonly string $smtpHost,
private readonly int $smtpPort,
) {}
public function send(string $recipient, string $message): NotificationResult
{
// Simulate sending email via SMTP
$messageId = uniqid('email_', true);
return new NotificationResult(
success: true,
messageId: $messageId,
channel: $this->getChannel(),
);
}
public function getChannel(): string { return 'email'; }
}
final class SmsNotification implements NotificationInterface
{
public function __construct(
private readonly string $apiKey,
private readonly string $fromNumber,
) {}
public function send(string $recipient, string $message): NotificationResult
{
$messageId = uniqid('sms_', true);
return new NotificationResult(
success: true,
messageId: $messageId,
channel: $this->getChannel(),
);
}
public function getChannel(): string { return 'sms'; }
}
final class PushNotification implements NotificationInterface
{
public function __construct(
private readonly string $firebaseKey,
) {}
public function send(string $recipient, string $message): NotificationResult
{
$messageId = uniqid('push_', true);
return new NotificationResult(
success: true,
messageId: $messageId,
channel: $this->getChannel(),
);
}
public function getChannel(): string { return 'push'; }
}
// [step] Define the abstract creator with the factory method
abstract class NotificationService
{
public function __construct(
protected readonly LoggerInterface $logger,
) {}
abstract protected function createNotification(): NotificationInterface;
public function notify(string $recipient, string $message): NotificationResult
{
$notification = $this->createNotification();
$this->logger->info("Sending via {$notification->getChannel()}", [
'recipient' => $recipient,
]);
try {
$result = $notification->send($recipient, $message);
if (!$result->success) {
$this->logger->error("Notification failed", ['error' => $result->error]);
}
return $result;
} catch (\Throwable $e) {
$this->logger->error("Notification exception", ['error' => $e->getMessage()]);
return new NotificationResult(
success: false,
messageId: '',
channel: $notification->getChannel(),
error: $e->getMessage(),
);
}
}
}
// [step] Concrete creators
final class EmailNotificationService extends NotificationService
{
public function __construct(
LoggerInterface $logger,
private readonly string $smtpHost = 'smtp.example.com',
private readonly int $smtpPort = 587,
) {
parent::__construct($logger);
}
protected function createNotification(): NotificationInterface
{
return new EmailNotification($this->smtpHost, $this->smtpPort);
}
}
final class SmsNotificationService extends NotificationService
{
public function __construct(
LoggerInterface $logger,
private readonly string $apiKey = '',
private readonly string $fromNumber = '',
) {
parent::__construct($logger);
}
protected function createNotification(): NotificationInterface
{
return new SmsNotification($this->apiKey, $this->fromNumber);
}
}Factory Method Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Factory Method Pattern in the Real World
“Think of a logistics company that ships packages. The headquarters defines the shipping process but doesn’t decide the vehicle. Regional offices (subclasses) choose whether to use trucks, ships, or drones based on local conditions. The headquarters just says ‘get me a transport’ and the regional office delivers the right one.”