DEV Community

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

Posted on • Edited on

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

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();
        }
    }
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
        }
    }
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

PaypalPayment.php

class PaypalPayment implements PayableInterface
{

    public function pay()
    {
        // Implement paypall payment logic
    }
}
Enter fullscreen mode Exit fullscreen mode

CreditCardPayment.php

class WiretransferPayment implements PayableInterface
{

    public function pay()
    {
        // Implement Wire transfer payment logic
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (8)

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

Good question please see above
comment by @connor leech

Collapse
 
firecentaur profile image
Paul Preibisch

Please give credit to the original author of your example Katerina Trajchevska - the same example comes from this video: youtube.com/watch?v=rtmFCcjEgEw

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

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

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

Collapse
 
dineshwasnik profile image
Dinesh Wasnik

@Abrar ahmad ,,

You can achieve OCP by using trait also..

What you think about this.