Design patterns are proven solutions to common software design problems. They help improve code readability, maintainability and scalability.
In this article, we'll explore how to implement some of the most commonly used design patterns, Factory, Singleton and Dependency Injection, in real-world PHP applications using PHP 8.
1. Factory Pattern
Scenario
Imagine you’re developing an e-commerce application where different types of payment methods are available: Credit Card, PayPal and Bitcoin. Instead of creating each payment object directly, you can use the Factory Pattern.
Implementation
Step 1: Define the Payment Interface
interface PaymentMethod {
public function processPayment(float $amount);
}
Step 2: Create Concrete Payment Classes
class CreditCardPayment implements PaymentMethod {
public function processPayment(float $amount) {
return "Processing a credit card payment of $$amount.";
}
}
class PayPalPayment implements PaymentMethod {
public function processPayment(float $amount) {
return "Processing a PayPal payment of $$amount.";
}
}
class BitcoinPayment implements PaymentMethod {
public function processPayment(float $amount) {
return "Processing a Bitcoin payment of $$amount.";
}
}
Step 3: Implement the Factory Class
class PaymentFactory {
public static function create(string $type): PaymentMethod {
switch ($type) {
case 'credit_card':
return new CreditCardPayment();
case 'paypal':
return new PayPalPayment();
case 'bitcoin':
return new BitcoinPayment();
default:
throw new InvalidArgumentException("Unknown payment method");
}
}
}
Step 4: Usage Example
$paymentMethod = PaymentFactory::create('paypal');
echo $paymentMethod->processPayment(100.00); // Outputs: Processing a PayPal payment of $100.
Benefits
- Decoupling: You decouple the client code from the concrete payment classes.
- Scalability: Adding new payment methods becomes easier, as you only need to create a new class and update the factory.
Google Ad 1
2. Singleton Pattern
Scenario
Let’s consider a logging mechanism for your application. You want to ensure that only one instance of the logger exists to avoid writing to multiple files or connections.
Implementation
Step 1: Create the Logger Class
class Logger {
private static ?Logger $instance = null;
private function __construct() {
// Private constructor to prevent instantiation
}
public static function getInstance(): Logger {
if (self::$instance === null) {
self::$instance = new Logger();
}
return self::$instance;
}
public function log(string $message) {
// In a real application, you'd write to a file or database
echo "[LOG] " . $message . "\n";
}
}
Step 2: Usage Example
$logger = Logger::getInstance();
$logger->log("This is a log message."); // Outputs: [LOG] This is a log message.
Benefits
- Controlled Access: Only one instance is created, which saves resources.
- Global Point of Access: The logger can be accessed from anywhere in the application.
Google Ad 2
3. Dependency Injection
Scenario
In a web application, you might have a service that retrieves user information from a database. You want to make it easy to test this service by allowing it to receive its database dependency through a constructor.
Implementation
Step 1: Create the Database Class
class Database {
public function connect() {
// Simulate a database connection
return "Database connected";
}
}
Step 2: Create the User Service Class
class UserService {
private Database $database;
public function __construct(Database $database) {
$this->database = $database;
}
public function getUser(int $id) {
// Simulate fetching a user
return $this->database->connect() . " - User with ID: $id";
}
}
Step 3: Usage Example
$database = new Database();
$userService = new UserService($database);
echo $userService->getUser(1); // Outputs: Database connected - User with ID: 1
Benefits
- Loose Coupling: UserService is not tightly coupled with the Database class.
- Testability: You can easily mock the Database dependency in unit tests.
Conclusion
Implementing design patterns in PHP applications can significantly enhance code structure and maintainability. The Factory Pattern allows for flexible object creation, the Singleton Pattern ensures controlled access to shared resources and Dependency Injection promotes loose coupling and testability.
Thanks for reading the article, for more Science & Technology related articles read and subscribe to peoples blog articles.