DEV Community

Cover image for SOLID in PHP
Emmanuel Valverde Ramos
Emmanuel Valverde Ramos

Posted on • Edited on

SOLID in PHP

What is SOLID? 🙄

It is a set of principles to have good software design practices compiled by Uncle Bob.

Why should I use them?

  • Software design principles or conventions.
  • Widely accepted in the industry.
  • Help make code more maintainable and tolerant to changes.
  • Applicable in terms of class design (micro-design), and also at the software architecture level.

If you don't use SOLID, you probably code STUPID¹ without knowing it

¹: STUPID stands for: Singleton, Tight Coupling, Untestability, Premature Optimization, Indescriptive Naming, Duplication

What does SOLID stands for

SOLID is an acronym that stands for:

  • Single responsibility principle
  • Open/closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

What do we do

S - Single Responsibility Principle(SRP)

Alt Text

💡 A class should only have one reason to change, which means it should only have one responsibility.

  • How to accomplish
    • Small classes with limited objectives
  • Purpose or gain:
    • High cohesion and robustness
    • Allow class composition (inject collaborators)
    • Avoid code duplication

Example

Let's imagine we have a class that represents a text document, said document has a title and content. This document must be able to be exported to HTML and PDF.

Violation of the SRP 👎

⚠️ Code coupled with more than one responsibility

class Document
{
    protected $title;
    protected $content;

    public function __construct(string $title, string $content)
    {
        $this->title = $title;
        $this->content= $content;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function getContent(): string
    {
        return $this->content;
    }

        public function exportHtml() {
                echo "DOCUMENT EXPORTED TO HTML".PHP_EOL;
        echo "Title: ".$this->getTitle().PHP_EOL;
        echo "Content: ".$this->getContent().PHP_EOL.PHP_EOL;
        }

        public function exportPdf() {
                echo "DOCUMENT EXPORTED TO PDF".PHP_EOL;
        echo "Title: ".$this->getTitle().PHP_EOL;
        echo "Content: ".$this->getContent().PHP_EOL.PHP_EOL;
        }
}
Enter fullscreen mode Exit fullscreen mode

As you may see the methods or functions that we expose as APIs for other programmers to use, include the getTitle() and the getContent() but these methods are being used within the behavior of the same class.

This breaks the Tell-Don't-Ask principle:

💬 Tell-Don't-Ask is a principle that helps people remember that object-orientation is about bundling data with the functions that operate on that data. It reminds us that rather than asking an object for data and acting on that data, we should instead tell an object what to do.

Finally, we also see that the class that must represent a document not only has the responsibility of representing it, but also of exporting it in different formats.

Following the SRP Principle 👍

Once we have identified that the Document class should not have anything other than the representation of a "document", the next thing we have to establish is the API through which we want to communicate with the export.

For the export we will need to create an interface that receives a document.

interface ExportableDocumentInterface
{
    public function export(Document $document);
}
Enter fullscreen mode Exit fullscreen mode

The next thing we have to do is extract the logic that does not belong to the class.

class HtmlExportableDocument implements ExportableDocumentInterface
{
    public function export(Document $document)
    {
        echo "DOCUMENT EXPORTED TO HTML".PHP_EOL;
        echo "Title: ".$document->getTitle().PHP_EOL;
        echo "Content: ".$document->getContent().PHP_EOL.PHP_EOL;
    }
}
Enter fullscreen mode Exit fullscreen mode
class PdfExportableDocument implements ExportableDocumentInterface
{
    public function export(Document $document)
    {
        echo "DOCUMENT EXPORTED TO PDF".PHP_EOL;
        echo "Title: ".$document->getTitle().PHP_EOL;
        echo "Content: ".$document->getContent().PHP_EOL.PHP_EOL;
    }
}
Enter fullscreen mode Exit fullscreen mode

Leaving the class implementation something like this

class Document
{
    protected $title;
    protected $content;

    public function __construct(string $title, string $content)
    {
        $this->title = $title;
        $this->content= $content;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function getContent(): string
    {
        return $this->content;
    }
}
Enter fullscreen mode Exit fullscreen mode

This makes it easier for both the exports and the documentation class to be better tested.

O - Open-Closed Principle(OCP)

Alt Text

💡 Objects or entities should be open for extension, but closed for modification.

  • How to accomplish
    • Avoiding depending on specific implementations, making use of abstract classes or interfaces.
  • Purpose or gain:
    • Makes it easy to add new use cases to our application

Examples

Let's imagine that we need to implement a login system. Initially to authenticate our user we need a username and a password (Main use case), so far so good, but what happens if we require or from business require that the user authenticate through Twitter or Gmail?

To begin with, if a situation like this arises, it is important to understand that what is being asked of us is a new feature and not that we modify the current one. And that the case of Twitter would be one use case and that of Gmail another totally different.

Third Party API Login - OCP Violation 👎

class LoginService
{
    public function login($user)
    {
        if ($user instanceof User) {
            $this->authenticateUser($user);
        } else if ($user instanceOf ThirdPartyUser) {
            $this->authenticateThirdPartyUser($user);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Login with third party API - Following OCP 👍

The first thing we should do is create an interface that complies with what we want to do and that fits the specific use case.

interface LoginInterface
{
    public function authenticateUser(UserInterface $user);
}
Enter fullscreen mode Exit fullscreen mode

Now we should decouple the logic that we had already created for our use case and implement it in a class that implements our interface.

class UserAuthentication implements LoginInterface
{
    public function authenticateUser(UserInterface $user)
    {
        // TODO: Implement authenticateUser() method.
    }
}
Enter fullscreen mode Exit fullscreen mode
class ThirdPartyUserAuthentication implements LoginInterface
{
    public function authenticateUser(UserInterface $user)
    {
        // TODO: Implement authenticateUser() method.
    }
}
Enter fullscreen mode Exit fullscreen mode
class LoginService
{
    public function login(LoginInterface $loginService, UserInterface $user)
    {
        $loginService->authenticateUser($user);
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the LoginService class is agnostic of which authentication method (via web, via google or twitter, etc).

Payments API implemented with a switch - OCP Violation 👎

A very common case is when we have a switch(), where each case performs a different action and there is the possibility that in the future we will continue adding more cases to the switch. Let's look at the following example.

Here we have a controller with a pay() method, which is responsible for receiving the type of payment through the request and, depending on that type, the payment will be processed through one or another method found in the Payment class.

public function pay(Request $request)
{
    $payment = new Payment();

    switch ($request->type) {
        case 'credit':
            $payment->payWithCreditCard();
            break;
        case 'paypal':
            $payment->payWithPaypal();
            break;
        default:
            // Exception
            break;
    }
}
Enter fullscreen mode Exit fullscreen mode
class PaymentRequest
{
    public function payWithCreditCard()
    {
        // Logic to pay with a credit card...
    }

    public function payWithPaypal()
    {
        // Logic to pay with paypal...
    }
}
Enter fullscreen mode Exit fullscreen mode

This code has 2 big problems:

  • We should add one more case for each new payment that we accept or delete a case in the event that we do not accept more payments through PayPal.
  • All the methods that process the different types of payments are found in a single class, the Payment class. Therefore, when we add a new payment type or remove one, we should edit the Payment class, and as the Open / Closed principle says, this is not ideal. Like it is also violating the principle of Single Responsibility.

This violation it's also related to a code smell call Switch Statements Smell if you want to know more about refactors or examples go into refactoring guru

Payments API implemented with a switch - Following OCP 👍

The first thing we could do to try to comply with the OCP is to create an interface with the pay() method.

interface PayableInterface
{
    public function pay();
}
Enter fullscreen mode Exit fullscreen mode

Now we will proceed to create the classes that should implement these interfaces.

class CreditCardPayment implements PayableInterface
{
    public function pay()
    {
        // Logic to pay with a credit card...
    }
}
Enter fullscreen mode Exit fullscreen mode
class PaypalPayment implements PayableInterface
{
    public function pay()
    {
        // Logic to pay with paypal...
    }
}
Enter fullscreen mode Exit fullscreen mode

The next step would be to refactor our pay() method.

public function pay(Request $request)
{
    $paymentFactory = new PaymentFactory();
    $payment = $paymentFactory->initialize($request->type);

    return $payment->pay();
}
Enter fullscreen mode Exit fullscreen mode

👁 As you can see, we have replaced the switch with a factory.

class PaymentFactory
{
    public function initialize(string $type): PayableInterface
    {
        switch ($type) {
            case 'credit':
                return new CreditCardPayment();
            case 'paypal':
                return new PayPalPayment();
            default:
                throw new \Exception("Payment method not supported");
                break;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits of the Open / Closed Principle

  • Extend the functionalities of the system, without touching the core of the system.
  • We prevent breaking parts of the system by adding new functionalities.
  • Ease of testing.
  • Separation of the different logics.

🎨 Design patterns that we can find useful for OCP

L - Liskov Substitution Principle(LSP)

Alt Text

This principle was introduced by the guru and the first woman in America to earn a Ph.D. in Computer Science, Barbara Liskov. And it is a very interesting principle.

According to Wikipedia. Liskov's Substitution Principle says that each class that inherits from another can be used as its parent without having to know the differences between them.

  • Concepts:
    • If S is a subtype of T, instances of T should be substitutable for instances of S without altering the program properties, That is, by having a hierarchy it means that we are establishing a contract in the father, so ensuring that this contract is maintained in the child will allow us to replace the father and the application will continue to work perfectly.
  • How to accomplish:
    • The behavior of the sub-classes must respect the contract established in the super-class.
    • Maintain functional correctness to be able to apply OCP.

There are 3 important points that we have to keep in mind in order not to violate the Liskov principle:

  • Not to strengthen the pre-conditions and not to weaken the post-conditions of the parent class (defensive programming).
  • The invariants set in the base class must be kept in the subclasses.
  • Cannot be a method in the subclass that goes against a behavior of the base class. This is called Historical Constraint.

Example

Shipping calculation

Let's say we have a Shipping class that is going to calculate the shipping cost of a product given its weight and destination.

class Shipping
{
    public function calculateShippingCost($weightOfPackageKg, $destiny)
    {
        // Pre-condition:
        if ($weightOfPackageKg <= 0) {
            throw new \Exception('Package weight cannot be less than or equal to zero');
        }

        // We calculate the shipping cost by
        $shippingCost = rand(5, 15);

        // Post-condition
        if ($shippingCost <= 0) {
            throw new \Exception('Shipping price cannot be less than or equal to zero');
        }

        return $shippingCost;
    }
}
Enter fullscreen mode Exit fullscreen mode

Shipping calculation - LSP violation due to behavior change in daughter class 👎

class WorldWideShipping extends Shipping
{
    public function calculateShippingCost($weightOfPackageKg, $destiny)
    {
        // Pre-condition
        if ($weightOfPackageKg <= 0) {
            throw new \Exception('Package weight cannot be less than or equal to zero');
        }

        // We strengthen the pre-conditions
        if (empty($destiny)) {
            throw new \Exception('Destiny cannot be empty');
        }

        // We calculate the shipping cost by
        $shippingCost = rand(5, 15);

        // By changing the post-conditions we allow there to be cases
        // in which the shipping is 0
        if ('Spain' === $destiny) {
            $shippingCost = 0;
        }

        return $shippingCost;
    }
}
Enter fullscreen mode Exit fullscreen mode

The problem is that we generate with a class like the previous one is that we are exposing a similar API for programmers, but that has a different implementation.

This class will be the parent class of our example where its method to calculate the shipping price has a pre and a post condition (this way of programming using the pre and post conditions is called Defensive Programming).

For example, a programmer on our team is sure that the calculateShippingCost() method, of the Shipping class, allows null destination and shipping costs greater than zero, so by wanting to use the WorldWideShipping class, it could cause the system to burst, for example, if you want to use the result of calculateShippingCost() in a slice or by giving it a null destiny.

Therefore, the WorldWideShipping class is violating the Liskov substitution principle.

Shipping calculation - LSP violation due to change of invariants from child class 👎

The invariants are values ​​of the parent class that cannot be modified by the child classes.

Let's say we want to modify the Shipping class that we had before and we want to make the limit of the weight per kilos be 0 but that it is in a variable.

class Shipping
{
    protected $weightGreaterThan = 0;

    public function calculateShippingCost($weightOfPackageKg, $destiny)
    {
        // Pre-condition:
        if ($weightOfPackageKg <= $this->weightGreaterThan) {
            throw new \Exception("Package weight cannot be less than or equal to {$this->weightGreaterThan}");
        }

        // We calculate the shipping cost by
        $shippingCost = rand(5, 15);

        // Post-condition
        if ($shippingCost <= 0) {
            throw new \Exception('Shipping price cannot be less than or equal to zero');
        }

        return $shippingCost;
    }
}
Enter fullscreen mode Exit fullscreen mode
class WorldWideShipping extends Shipping
{
    public function calculateShippingCost($weightOfPackageKg, $destiny)
    {
    // We modify the value of the parent class
        $this->weightGreaterThan = 10;

        // Pre-condition
        if ($weightOfPackageKg <= $this->weightGreaterThan) {
            throw new \Exception("Package weight cannot be less than or equal to {$this->weightGreaterThan}");
        }
    // Previous code...
    }
}
Enter fullscreen mode Exit fullscreen mode

Shipping calculation - Following LSP by change of invariants from child class 👍

The easiest way to avoid this would be to simply create the variable $weightOfPackageKg as a private constant if our version of PHP (7.1.0) allows it but by creating that private variable.

Historical Restrictions

Historical restrictions say that there cannot be a method in the child class that goes against a behavior of its parent class.

That is, if in the parent class there is the FixedTax() method, then the ModifyTax() method cannot exist in the child class. Or didn't they teach you not to disobey your parents? 😆.

For a method of the subclass to modify the value of a property of the base class is a violation of the Liskov principle because classes must be able to change the value of their properties only (Encapsulation).

The easiest way not to break the LSP

The best way not to break LSP is by using Interfaces. Instead of extending our child classes from a parent class.

interface CalculabeShippingCost
{
    public function calculateShippingCost($weightOfPackageKg, $destiny);
}
Enter fullscreen mode Exit fullscreen mode
class WorldWideShipping implements CalculabeShippingCost
{
    public function calculateShippingCost($weightOfPackageKg, $destiny)
    {
        // Implementation of logic
    }
}
Enter fullscreen mode Exit fullscreen mode

By using interfaces you can implement methods that various classes have in common, but each method will have its own implementation, its own pre and post conditions, its own invariants, etc. We are not tied to a parent class.

⚠️ This does not mean that we start using interfaces everywhere, although they are very good. But sometimes it is better to use base classes and other times interfaces. It all depends on the situation.

Interfaces 🆚 Abstract Class

  • Interface benefits
    • Does not modify the hierarchy tree
    • Allows to implement N Interfaces
  • Benefits of Abstract Class
    • It allows to develop the Template Method¹ pattern by pushing the logic to the model. Problem: Difficulty tracing who the actors are and when capturing errors
    • Private getters (Tell-Don't-Ask principle)

¹. Design pattern Template Method: It states that in the abstract class we would define a method body that defines what operation we are going to perform, but we would be calling some methods defined as abstract (delegating the implementation to the children). But beware! 👀 this implies a loss of traceability of our code.

Conclusion of Interfaces 🆚 Abstract Class

  • When do we use Interfaces?: When we are going to decouple between layers.

  • When do we use Abstract?: In certain cases for Domain Models (Domain models not ORM models, to avoid anemic domain models)

🎨 Design patterns that can be useful to us in the LSP

I - Interface Segregation Principle (ISP)

Alt Text

💡 A client should only know the methods they are going to use and not those that they are not going to use.

Basically, what this principle refers to is that we should not create classes with thousands of methods where it ends up being a huge file. Since we are generating a monster class, where most of the time we will only use some of its methods each time. And for that it refers to the need for interfaces, it is also important to understand that this helps a lot at the Single Responsibility Principle(SRP).

  • How to accomplish
    • Define interface contracts based on the clients that use them and not on the implementations that we could have (The interfaces belong to the clients).
    • Avoid Header Interfaces by promoting Role Interfaces
  • Purpose or gain:
    • High cohesion and low structural coupling

Header Interfaces

Martin fowler in the article HeaderInterface sustain.

💬 A header interface is an explicit interface that mimics the implicit public interface of a class. Essentially you take all the public methods of a class and declare them in an interface. You can then supply an alternative implementation for the class. This is the opposite of a RoleInterface - I discuss more details and the pros and cons there.

Role Interfaces

Martin fowler in the article RoleInterface sustain.

💬 A role interface is defined by looking at a specific interaction between suppliers and consumers. A supplier component will usually implement several role interfaces, one for each of these patterns of interaction. This contrasts to a HeaderInterface, where the supplier will only have a single interface.

Examples

Simple Example

We want to be able to send notifications via email, Slack, or txt file. What signature will the interface have? 📨

  • a) $notifier($content) ✔️
  • b) $notifier($slackChannel, $messageTitle, $messageContent, $messageStatus) ❌
  • c) $notifier($recieverEmail, $emailSubject, $emailContent) ❌
  • d) $notifier($destination, $subject, $content) ❌
  • e) $notifier($filename, $tag, $description) ❌

We can rule out that options B, C and E, since Header Interface would be based on the implementation (for Slack, email and file respectively).

In the case of option D, we could consider it invalid given that the type $destination It does not offer us any specificity (we do not know if it is an email, a channel ...).

Finally, in option A, we would only be sending the content, so the particularities of each of the types of notification would have to be given in the constructor (depending on the use case you could not always).

👁 The interfaces belong to the clients and not to those who implement them.

Example Developer | QA | PM - ISP violation due to excess responsibilities and poor abstraction 👎

A simple example would be the following situation. Let's imagine that we have developers, a QA team and a project manager who have to determine whether to program.

Let's say the programmer can program and test, while the QA can only test.

interface Workable
{
    public function canCode();
    public function code();
    public function test();
}
Enter fullscreen mode Exit fullscreen mode
class Developer implements Workable
{
    public function canCode()
    {
        return true;
    }
    public function code()
    {
        return 'coding';
    }
    public function test()
    {
        return 'testing in localhost';
    }
}
Enter fullscreen mode Exit fullscreen mode
class Tester implements Workable
{
    public function canCode()
    {
        return false;
    }
    public function code()
    {
        // El QA no puede programar
         throw new Exception('Opps! I can not code');
    }
    public function test()
    {
        return 'testing in test server';
    }
}
Enter fullscreen mode Exit fullscreen mode
class ProjectManagement
{
    public function processCode(Workable $member)
    {
        if ($member->canCode()) {
            $member->code();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

If we pay attention we will see that the Tester class has a method that does not correspond to it since it is not called and if it is called it would give us an Exception.

So we should make a small refactor to be able to comply with the principle of segregation of interfaces.

Example Developer | QA | PM - Following ISP 👍

The first thing is to identify what actions we have to perform, design the interfaces and assign these interfaces to the corresponding actors depending on the use case.

interface Codeable
{
    public function code();
}
Enter fullscreen mode Exit fullscreen mode
interface Testable
{
    public function test();
}
Enter fullscreen mode Exit fullscreen mode
class Programmer implements Codeable, Testable
{
    public function code()
    {
        return 'coding';
    }
    public function test()
    {
        return 'testing in localhost';
    }
}
Enter fullscreen mode Exit fullscreen mode
class Tester implements Testable
{
    public function test()
    {
        return 'testing in test server';
    }
}
Enter fullscreen mode Exit fullscreen mode
class ProjectManagement
{
    public function processCode(Codeable $member)
    {
        $member->code();
    }
}
Enter fullscreen mode Exit fullscreen mode

This code does comply with the principle of segregation of interfaces. As with the previous principles.

🎨 Design patterns that can be useful to us in the ISP

D - Dependency Inversion Principle (DIP)

Alt Text

I must first make it clear that Dependency Injection is NOT the same as Dependency Inversion. Dependency inversion is a principle, while dependency injection is a design pattern.

💡 High-level modules should not depend on low-level ones. Both should depend on abstractions

  • How to accomplish
    • Inject dependencies (parameters received in constructor).
    • Rely on the interfaces (contracts) of these dependencies and not on specific implementations.
    • LSP as a premise.
  • Purpose or gain:
    • Facilitate the modification and replacement of implementations.
    • Better class testability

The principle of dependency injection tries to maintain a low coupling.

Laravel controller example

Let's say we have a UserController. What in its index method what it does is return a JSON list of users with the users created the previous day.

Laravel controller - DIP Violation 👎

public function index()
    {
        $users = new User();
        $users = $users->where('created_at', Carbon::yesterday())->get();

        return response()->json(['users' => $users]);
    }
}
Enter fullscreen mode Exit fullscreen mode

This code wouldn't be bad, because it would clearly work. But at the same time it would generate the following problems:

  • We cannot reuse the code as we are tied to Eloquent.
  • It is difficult to test the methods that instantiate one or several objects (high coupling), since it is difficult to verify that it is failing.
  • It breaks the principle of single responsibility, because, in addition to the method doing its job, it also has to create the objects in order to do its job.

Laravel controller - Following the DIP 👍

interface UserRepositoryInterface
{
    // 👁 I am returning an array
    // but it should return Domain Models
    public function getUserFromYesterday(DateInterface $date): array;
}
Enter fullscreen mode Exit fullscreen mode
class UserEloquentRepository implements UserRepositoryInterface
{
    public function getUserFromYesterday(DateInterface $date): array
    {
        return User::where('created_at', '>', $date)
            ->get()
            ->toArray();
    }
}
Enter fullscreen mode Exit fullscreen mode
class UserSqlRepository implements UserRepositoryInterface
{
    public function getUserFromYesterday(DateInterface $date): array
    {
        return \DB::table('users')
            ->where('created_at', '>', $date)
            ->get()
            ->toArray();
    }
}
Enter fullscreen mode Exit fullscreen mode
class UserCsvRepository implements UserRepositoryInterface
{
    public function getUserFromYesterday(DateInterface $date): array
    {
        // 👁 I am accessing the infrastructure
        // from the same method maybe not the best
        $fileName = "users_created_{$date}.csv";
        $fileHandle = fopen($fileName, 'r');

        while (($users[] = fgetscsv($fileHandle, 0, ","))  !== false) {
        }

        fclose($fileHandle);

        return $users;
    }
}
Enter fullscreen mode Exit fullscreen mode

As we can see, all classes implement the UserRespositoryInterface interface. And this gives us the freedom to get the users either from Eloquent, fromSQL or from a CSV 👏😲 file.

This is fine and would work in a normal application, but how do we make the Laravel controller receive that repository in its index method?

The answer is registering the interface with which class it has by default.

namespace App\Providers;

use Illuminate\Support\ServiceProvider;


class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(
            'App\Repositories\Contracts\UserRepositoryInterface',
            'App\Repositories\UserCsvRepository'
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

In other frameworks like Symfony it can be done using PHP DI.

Conclusions

In order to make a more maintainable, reusable and testable code we should try to implement these principles. Like we should know the design patterns that could be useful when we use these principles.

Webgraphy

🇪🇸

(🇬🇧/🇺🇸)

Top comments (36)

Collapse
 
rescatado182 profile image
Diego Pinzón • Edited

Hello Emmanuel, great article, really. I'm looking for a complete guide and with this I really find it and help me to learning these principles and how to implement on my work. I have a question, on LCP principle, and example two (invariants), do you explain: "The easiest way to avoid this would be to simply create the variable $weightOfPackageKg as a private constant" -> do you mean really to this var, or the class Shipping propiety $weightGreaterThan ?

Collapse
 
drobinsonsudo profile image
David Robinson

Great explanation! The code samples (correct vs. incorrect) are great - they really helped illustrate the principles well. I am definitely bookmarking this. Thanks for putting so much work into this and sharing.

Collapse
 
evrtrabajo profile image
Emmanuel Valverde Ramos

First of all thank you for the comment.

And on the other hand to tell you that thanks to comments like this I have the intention of continuing to post like this

Collapse
 
ahmardiy profile image
AhmardiY

Wow, this is really great.

Collapse
 
dardsmind profile image
dardsmind

This is the best explanation of SOLID Principles I found on the internet, I really like the drawing illustration as it explain well.

Collapse
 
m1tko profile image
Dimitar Kalenderov

Very good explanation of the SOLID principles. Thank you!

Collapse
 
carlosrenatohr profile image
~👨🏻‍💻🇳🇮

In the middle of 2021-pandemic-chaos, this post came to refresh and enhance some pieces of my daily coding. I really appreciated the effort and time invested on this keystone summary as it's SOLID for coding in a good way.

Collapse
 
evrtrabajo profile image
Emmanuel Valverde Ramos

I'm really happy that the post helped you

Collapse
 
flagoon profile image
Pawel Kochanek

I find this article by mistake, but it's awesome explanation of SOLID principles. By myself I understand only first two. The other three are nicely explained.

Collapse
 
evrtrabajo profile image
Emmanuel Valverde Ramos

First of all, thank you for the feedback, I would like the post to have more visibility but you can see the algorithm of dev.to it's not helping

Collapse
 
squidbe profile image
squidbe • Edited

Your article showed up in "Top 7 Posts From Last Week" (which is how I found it), so the algorithm doesn't seem to be hurting you. 🙂

Thread Thread
 
evrtrabajo profile image
Emmanuel Valverde Ramos

Hahahah, Yes so it seems, but I have a small reader in which I read the dev.to via the API, and I haven't see my post on the api :D

Collapse
 
xedinunknown profile image
Anton Ukhanev

Pretty good practical explanation! Here I go into philosophical detail about why those principles are that way, how they relate to Separation of Concerns, and how they are coherent with the natural way things are.

dev.to/xedinunknown/separation-of-...

Collapse
 
evrtrabajo profile image
Emmanuel Valverde Ramos

😍 Love it

Collapse
 
krmgns profile image
Kerem Güneş

Cool article Emmanuel, thank you.

PS: PaymentFactory.initialize() can have PayableInterface return type, by the way.

Collapse
 
evrtrabajo profile image
Emmanuel Valverde Ramos

Updated the signature of the method to public function initialize(string $type): PayableInterface Thank you for the feedback

Collapse
 
jonathangreco profile image
Jonathan • Edited

Learning by making mistakes is my mojo, the way you explained SOLID is the best to teach me how it's really working. Thank you

Collapse
 
evrtrabajo profile image
Emmanuel Valverde Ramos

🙏 thanks really for saying that, I'm glad my article can help you