Blog Details
Md. Raqibul Hasan
10 Oct 2024
3 min read
When writing code, it’s important to keep it clean and easy to understand. The SOLID principles help with this. These are five simple rules that make your code better. Let’s look at how they work in PHP.
SOLID stands for:
Let’s break them down.
Definition:
Each class should only have one job or responsibility. It should only focus on one thing. If your class is doing more than one thing, it’s harder to update, test, and understand.
Why use it?
When a class has only one task, it’s easier to manage. You can make changes to one part of the code without affecting other parts.
Example:
Let’s say you have a class that both manages user data and sends emails. This is too much responsibility for one class.
class UserManager {
public function getUser($id) {
// Fetch user from the database
}
public function sendEmail($email) {
// Send email to the user
}
}
This breaks SRP because it’s doing two things. It fetches users and sends emails. To follow SRP, we can split these responsibilities into two classes.
class UserManager {
public function getUser($id) {
// Fetch user from the database
}
}
class EmailService {
public function sendEmail($email) {
// Send email
}
}
Now, each class has a single responsibility, making the code easier to maintain.
Definition:
Your code should be open for extension but closed for modification. This means you can add new features or behaviors to your code without changing existing code.
Why use it?
If you modify existing code often, you might break something that was working. Instead, it’s better to extend it when adding new features.
Example:
Let’s say you have a class that handles payments through PayPal.
class PaymentService {
public function payWithPaypal() {
// Paypal payment process
}
}
// To call paypal payment
(new PaymentService())->payWithPaypal();
If you want to add a new payment method like Stripe, you could modify this class. But that would break OCP. Instead, Lets see how to extend it.
interface PaymentProcessor {
public function processPayment();
}
class PaypalPayment implements PaymentProcessor {
public function processPayment(){
// PayPal payment process
}
}
class StripePayment implements PaymentProcessor {
public function processPayment(){
// PayPal payment process
}
}
class PaymentService {
public function pay(PaymentProcessor $paymentProcessor){
$paymentProcessor->processPayment();
}
}
// To call paypal payment
(new PaymentService())->pay(new PaypalPayment());
// To call stripe payment
(new PaymentService())->pay(new StripePayment());
Now, you can add new payment methods without changing the original PaymentService
class.
Definition:
Subclasses should be able to replace their parent classes without breaking the code. This means the new class should work the same way as the original class.
Why use it?
It ensures that subclasses can be used anywhere the parent class is used, without causing bugs or errors.
Example:
Let’s say you have a base class for Bird
and a subclass for Penguin
.
abstract class Bird {
abstract public function fly();
}
class Penguin extends Bird {
public function fly() {
throw new Exception("Penguins can't fly!");
}
}
Here, calling the fly()
method on a Penguin
throws an exception, which is not expected if you're dealing with a general Bird
. The code breaks because Penguin
does not behave like a Bird
in this case. It violates LSP because Penguin
cannot fully replace Bird
.
class Bird {
// Common properties and methods for all birds
}
abstract class FlyingBird extends Bird {
abstract public function fly() ;
}
class Penguin extends Bird {
// Penguins don't fly, so no fly() method here
}
class Penguin extends Bird {
// Penguins don't fly, so no fly() method here
}
class Pegeon extends FlyingBird {
public function fly(){
return "Flying!";
}
}
We no longer try to force Penguin
to behave like a flying bird and everything works as expected. This follows LSP because Penguin
behaves like a Bird
and Pegeon
behaves like a FlyingBird
. The code doesn’t break when substituting FlyingBird
for Bird
. By separating flying and non-flying birds, we respect the Liskov Substitution Principle
Definition:
A class should not be forced to implement methods it doesn’t use. Instead of having one large interface, split it into smaller, more specific interfaces.
Why use it?
If a class has to implement methods it doesn’t need, the code becomes bloated and harder to maintain.
Example:
Suppose we have an interface for workers that includes both work and eating methods.
interface Worker {
public function work();
public function eat();
}
If we create a class for a robot that only works and doesn’t eat, we’re forced to implement the eat()
method even though it’s not needed. To follow ISP, we can break the interface into two smaller ones.
interface Worker {
public function work();
}
interface Eater {
public function eat();
}
class Robot implements Worker {
public function work() {
// Robot working
}
}
class Human implements Worker, Eater {
public function work() {
// Human working
}
public function eat() {
// Human working
}
}
Now, Robot
only implements the work()
method and doesn’t need to worry about eating. Human
implements work()
and eat()
methods.
Definition:
High-level classes should not depend on low-level classes. Both should depend on abstractions (like interfaces), not on concrete implementations.
Why use it?
When you directly depend on specific classes, it’s harder to swap them out if you need to make changes. Using abstractions makes your code more flexible and easier to maintain.
Example:
If a class directly creates its dependencies, like in the example below, it’s hard to switch to a different mail service later.
class Notification {
public function sendEmail() {
// Code to send email
}
}
Instead, you can inject the dependency through an interface, making it easier to change the email service without modifying the Notification
class.
interface MailerInterface {
public function send();
}
class MailerService implements MailerInterface {
public function send(){
// codes for sending mail
}
}
class Notification {
public function __construct(protected MailerInterface $mailer) {}
public function send() {
$this->mailer->send();
}
}
// to send mail notification
(new Notification(new MailerService()))->send();
Now, the Notification
class depends on the MailerInterface
, which makes it flexible. You can easily swap out the mail service without changing the code.
The SOLID principles are simple but powerful rules that make your PHP code better. They help you write cleaner, more flexible, and easier-to-understand code. By following these principles, your projects will be easier to maintain, less buggy, and more adaptable to new features or changes. Start using these principles in your projects to see the difference!
Don’t worry, we don’t spam!