loading...
Cover image for Becoming a better Laravel developer by using the SOLID design principles. Part 2

Becoming a better Laravel developer by using the SOLID design principles. Part 2

abrardev99 profile image Abrar Ahmad Updated on ・3 min read

This article is part 2 of Becoming a better Laravel developer by using the SOLID design principles article series. You can see part one S - Single-responsibility principle here

Today, we will discuss the second letter of SOLID which is O - Open-closed principle.

What is O - Open-closed principle

According to the official definition

software entities should be open for extension, but closed for modification.

Entities can be classes, modules, functions, etc. The definition meaning that extends functionality by adding new code instead of changing existing code.
It is quite confusing to understand by title. So let's see it with examples in Laravel.

Let's say we have an e-commerce system that involves payments. So, we have PaymentController and in that controller, we have pay method which has two payment methods for now 1) Credit Card 2) Paypal and looks like below:

PaymentController.php

public function pay(Request $request)
    {
        $payment = new Payment();
        if($type == 'credit'){
            // do credit card payments
            $payment->paymentWithCreditCard();
        }else {
            //do paypal payments
            $payment->paymentWithPaypal();
        }
    }

and our Payment class looks like this:
Payment.php

class Payment
{

    public function paymentWithCreditCard()
    {
        // logic for credit card payments 
    }

    public function paymentWithPaypal()
    {
        // logic for paypal payments
    }
}

What happens when we want to add a new payment method let's say wire transfer

In pay method we add a new if condition and call an appropriate method from the Payment class.
So the pay method looks like below:
PaymentController.php

public function pay(Request $request)
    {
        $payment = new Payment();
        if($type == 'credit'){
            // do credit card payments
            $payment->paymentWithCreditCard();
        }else if($type == 'paypal') {
            //do paypal payments
            $payment->paymentWithPaypal();
        }
        else {
            //do wire transfer payments
            $payment->paymentWithWiretransfer();
        }
    }

and modifying the Payment class:
Payment.php

class Payment
{

    public function paymentWithCreditCard()
    {
        // logic for credit card payments
    }

    public function paymentWithPaypal()
    {
        // logic for paypal payments
    }

    public function paymentWithWiretransfer()
    {
        // logic for wire transfer payments
    }
}

So what will happen next What if we have another payment method called Cash on Delivery, we have added another if and then a new method in the Payment class. Another payment method comes called Coupon Payment and we are again modifying classes. Remember what does this principle says, Open for extension but close for modifications. so we are breaking this principle here.
So, how to fix it, let's apply Open Close Principle here.

Let's make a new interface with a method named pay read more about interfaces in PHP.
PayableInterface.php

interface PayableInterface
{
    public function pay();
}

Make a class for each payment method and extend it with the PayableInterface interface.
CreditCardPayment.php

class CreditCardPayment implements PayableInterface
{

    public function pay()
    {
        // Implement Credit Card payment logic
    }
}

PaypalPayment.php

class PaypalPayment implements PayableInterface
{

    public function pay()
    {
        // Implement paypall payment logic
    }
}

CreditCardPayment.php

class WiretransferPayment implements PayableInterface
{

    public function pay()
    {
        // Implement Wire transfer payment logic
    }
}

Now, make a new class
PaymentFactory.php

class PaymentFactory
{
    public function initializePayment($type)
    {
        if($type == 'credit'){
            // do credit card payments
            return new CreditCardPayment;
        }else if($type == 'paypal') {
            //do paypal payments
            return new PaypalPayment;
        }
        else if ($type == 'wire') {
            //do wire transfer payments
            return new WiretransferPayment;
        }
    }
}

and finally our pay method in
PaymentController.php. This pay method asks PaymentFactory todo the payments and factory handle itself everything.

public function pay(Request $request)
    {
        $paymentFactory = new PaymentFactory();
        $paymentMethod = $paymentFactory->initializePayment($request->type);
        $paymentMethod->pay();
    }

So what will happen next What if we have another payment method called Cash on Delivery we will make a new class called CashOnDeliveryPayment.php and implement the same payableInterface which will give us pay method to put cash on delivery logic there.

That's it. We solve the issue of modifying the classes now if the new payment method comes we extend functionality by adding new class instead of modifying the existing one.

Please comment down below if you do not understand any part of the article.

Discussion

pic
Editor guide
Collapse
shamshadzaheer profile image
Shamshad Zaheer

In the PaymentFactory class, we are using the same if else statements to manage different payments. Don't you think we are breaking the rule only with implementing the logic with a different wayt?

Collapse
abrardev99 profile image
Abrar Ahmad Author

Good question please see above
comment by @connor leech

Collapse
brunonp_ profile image
Brüno 🤠

Excelente. If we have to add another payment method, we have to add another “else if” in initializePayment?

Collapse
abrardev99 profile image
Abrar Ahmad Author

No. As i explained in article please check, you don't need to change controller now. You need to add an other payment class and implement payableInterface. Then in payment class add if else type check and return appropriate class. Done

Collapse
connor11528 profile image
Connor Leech

Instead of using if-else there could be an array on the PaymentFactory class where the type maps to the class, like 'credit' => 'CreditCardPayment' and then instantiate the correct class based on the type. Definitely an opportunity for improvement by using constants here. That if-else is not so bueno imho. Thanks for the article! Looking forward to the rest :)

Thread Thread
abrardev99 profile image
Abrar Ahmad Author

Yes we can use this approach. As my examples are not perfect. There is a plenty of room for improvements.