Open/Closed Principle (OCP) By Using PHP : SOLID Principle
Tags: php, solidprinciples, openclosedprinciple, oop
Published: 2024-02-14 23:29:22

## Introduction:
In the world of software development, adhering to SOLID principles is essential for building maintainable and scalable applications. Today, we will dive into the Open-Closed Principle (OCP) and explore how it can be applied in PHP. Let’s unlock the potential of OCP!

## What is the Open-Closed Principle?
The Open-Closed Principle, one of the SOLID principles, states that software entities **(classes, modules, functions)** should be **open for extension** but **closed for modification**. In simpler terms, we should be able to extend the behavior of a class without modifying its existing code. This promotes code reusability and makes our applications more flexible and easier to maintain.

## OCP in Action: A Practical Example
Consider a scenario where we have a “CreditCardPayment” class responsible for processing payments in our e-commerce application. Initially, it only supports credit card payments.

```
class CreditCardPayment {
    private string $title;
    private string $accountNumber;
    private int $balance = 1000;
    private int $serviceCharges = 50;
    
    public function __construct(string $title, string $accountNumber) {
        $this->title = $title;
        $this->accountNumber = $accountNumber;
    }
    
    public function pay(int $number) {
        $last4Digits = substr($this->accountNumber, -4);
        $this->balance = ($this->balance - $number) - $this->serviceCharges;
        
        printf(
          "Hello %s,\nPay successfully against xxxx-%s, Your remaining balance is %d", 
          $this->title, 
          $last4Digits, 
          $this->balance
        );
    }
}

$creditCard = new CreditCardPayment("Muhammad Raza Bangi", "123456789");
$creditCard->pay(100);

// output:
Hello Muhammad Raza Bangi,
Pay successfully against xxxx-6789, Your remaining balance is 850
```

So far, our system has been processing payments only via credit card. Now, our **client has an additional request**: they want to integrate PayPal as another payment method, and PayPal has its own set of service charges. No worries, we can handle this. Instead of limiting our class to credit card payments, let’s make it more versatile. We’ll change the class name from CreditCardPayment to simply Payment. Because our system processing two payment methods so class name should be generic.

```
class Payment {
    private string $title;
    private string $accountNumber;
    private string $paymentType;
    
    private int $balance = 1000;
    private int $creditCardServiceCharges = 50;
    private int $paypalServiceCharges = 100;
    
    public function __construct(
          string $title, 
          string $accountNumber, 
          string $paymentType
    ) {
        $this->title = $title;
        $this->accountNumber = $accountNumber;
        $this->paymentType = $paymentType;
    }
    
    public function pay(int $number) {
        $last4Digits = substr($this->accountNumber, -4);
        
        if ($this->paymentType === 'credit') {
            $this->balance = ($this->balance - $number) - $this->creditCardServiceCharges;
        } else if ($this->paymentType === 'paypal') {
            $this->balance = ($this->balance - $number) - $this->paypalServiceCharges;
        } else {
            echo "Invalid payment type, expect paypal or credit";
            exit;
        }
        
        printf(
            "Hello %s,\nPay successfully against xxxx-%s, Your remaining balance is %d\n", 
            $this->title, 
            $last4Digits, 
            $this->balance
        );
    }
}

$paypal = new Payment("Muhammad Raza Bangi", "123456789", "paypal");
$paypal->pay(100);

$creditCard = new Payment("Muhammad Raza Bangi", "987654321", "credit");
$creditCard->pay(100);

//output
(paypal instance output)
Hello Muhammad Raza Bangi,
Pay successfully against xxxx-6789, Your remaining balance is 800

(creditcard instance output)
Hello Muhammad Raza Bangi,
Pay successfully against xxxx-4321, Your remaining balance is 850
```

Ah, just when I was enjoying my coffee, the client throws in another curveball: they want to integrate Stripe as yet another payment method. It’s like a never-ending adventure! But hey, no need to stress out. Instead of tearing apart our existing code again, let’s keep it calm and cool.

## Now Open/Closed Principle comes in action:
Now, let’s explore how the Open/Closed Principle can be put into practice. Here’s how we can achieve that by utilizing abstract classes or interfaces.

```
abstract class Payment {
    protected string $title;
    protected string $accountNumber;
    protected int $balance = 1000;
    protected int $last4Digits;
    
    public function __construct(
          string $title, 
          string $accountNumber
    ) {
        $this->title = $title;
        $this->accountNumber = $accountNumber;
        $this->last4Digits = substr($this->accountNumber, -4);
    }
    
    public abstract function pay(int $number);
}

class CreditCard extends Payment {
    private int $charges = 50;
    
    public function pay(int $number) {
        $this->balance = ($this->balance - $number) - $this->charges;
        
        printf(
            "Hello %s,\nPay successfully against xxxx-%s, Your remaining balance is %d\n", 
            $this->title, 
            $last4Digits, 
            $this->balance
        );
    }
}

class Paypal extends Payment {
    private $charges = 100;
    
    public function pay(int $number) {
        $this->balance = ($this->balance - $number) - $this->charges;
        
        printf(
            "Hello %s,\nPay successfully against xxxx-%s, Your remaining balance is %d\n", 
            $this->title, 
            $last4Digits, 
            $this->balance
        );
    }
}

$creditCard = new CreditCard("Muhammad Raza Bangi", "123456789");
$creditCard->pay(200);

$paypal = new Paypal("Muhammad Raza Bangi", "987654321");
$paypal->pay(200);

//output
(creditcard instance output)
Hello Muhammad Raza Bangi,
Pay successfully against xxxx-6789, Your remaining balance is 750

(paypal instance output)
Hello Muhammad Raza Bangi,
Pay successfully against xxxx-4321, Your remaining balance is 700
```

Instead of directly modifying existing code, we’ll establish a blueprint for different payment methods using an interface or an abstract class. This blueprint will define the common behavior expected from all payment methods. Here are the example where you can see my code is open for extension but close for modification. Now i can add multiple payment methods without update/change/modify my existing Payment class. By following this approach, we ensure that our code remains closed for modification. Adding a new payment method becomes a matter of creating a new class that conforms to the established blueprint, rather than altering existing code. This way, we maintain the integrity and extensibility of our codebase while adhering to the Open/Closed Principle.

## Lets Take an another example by using interface:

```
interface Payment {
    public function pay(int $number);
    public function accountDetails();
}

class CreditCard implements Payment {
    private string $title;
    private string $accountNumber;
    private int $balance = 1000;
    private int $charges = 50;
    
    public function __construct(string $title, string $accountNumber) {
        $this->title = $title;
        $this->accountNumber = $accountNumber;
    }
    
    public function pay(int $number) {
        $this->balance = ($this->balance - $number) - $this->charges;
        
        return $this->balance;
    }
    
    public function accountDetails() {
        $last4Digits = substr($this->accountNumber, -4);
        
        printf(
            "Hello %s,\nYour remaining balance is %d against xxxx-%s\n", 
            $this->title, 
            $this->balance,
            $last4Digits, 
        );
    }
}


class Paypal implements Payment {
    private string $title;
    private string $accountNumber;
    private int $balance = 1000;
    private int $charges = 100;
    
    public function __construct(string $title, string $accountNumber) {
        $this->title = $title;
        $this->accountNumber = $accountNumber;
    }
    
    public function pay(int $number) {
        $this->balance = ($this->balance - $number) - $this->charges;
        
        return $this->balance;
    }
    
    public function accountDetails() {
        $last4Digits = substr($this->accountNumber, -4);
        
        printf(
            "Hello %s,\nYour remaining balance is %d against xxxx-%s\n", 
            $this->title, 
            $this->balance,
            $last4Digits, 
        );
    }
}

function showDetails(Payment $payment) {
    $payment->accountDetails();
}

$creditCard = new CreditCard("Muhammad Raza Bangi", "123456789");
$creditCard->pay(100);
showDetails($creditCard);

$paypal = new Paypal("Muhammad Raza Bangi", "987654321");
$paypal->pay(200);
showDetails($paypal);

//output
(creditcard instance output)
Hello Muhammad Raza Bangi,
Your remaining balance is 850 against xxxx-6789

(paypal instance output)
Hello Muhammad Raza Bangi,
Your remaining balance is 700 against xxxx-4321
```

## Conclusion:
The Open-Closed Principle encourages us to write flexible and extensible code, making our applications more robust and easier to maintain. By adhering to OCP in PHP, specifically in Laravel, we can build software that adapts to change with grace. Embrace the OCP mindset, and your code will be on a path of continuous improvement! Happy coding! 🚀👩‍💻

Thank you for your precious time to read :)