S.O.L.I.D - Practices for Object Oriented Programming, design better software with code examples in PHP

Last updated Sep 3, 2023 Published Apr 12, 2016

The content here is under the Attribution 4.0 International (CC BY 4.0) license

Being able to write better is a achievement that every programmer want to. SOLID is a great thing to start to and particularly one of the best things to be followed by TDD.

Single responsibility

A class should have only a single responsibility (i.e. only one potential change in the software’s specification should be able to affect the specification of the class) [1] [2] [3].

<?php
class Upload {

    private $file;

    public function __construct($file)
    {
        $this->file = $file;
    }

    public function validate($data)
    {
        // apply validation rules
    }
       
    public function moveFile()
    {
        // move file
    }

    public function rename()
    {
        // rename file
    }
}

We have our class Upload doing too many things at once, named:

  • Validates the file
  • Move the file
  • Renaming the file
  • Lack of file object

A better approach to it is to create a new class to validate it, to move it and finally to handle it to a new place, let’s see some code:

<?php
class File {}

class ValidateFile {}

class MoveFile {}

class TransformFile {}

class Upload {

    private $file;

    public function __construct(File $file)
    {
        $this->file = $file;
    }

    public function validate($data)
    {
        // apply validation rules
    }
       
    public function moveFile()
    {
        // move file
    }

    public function rename()
    {
        // rename file
    }
}

Side note: Michael Feathers uses the SRP referring to improve code design on his talk [4].

Open/Close

software entities … should be open for extension, but closed for modification [1] [2] [3].

This principle is followed by many vendors to allow developers extend features without change the original source code. Lets first understand the problem we want to solve.

Let’s imagine we have a class to send data through a web service and at the time we have only the XML handler to send this but you client requested you to implement the feature to support JSON, what would you do?

<?php
class Webservice {

    public function sendXml($data) {
        // handle the send here
    }
}

In the code above we have unfortunately only one choice, add a method sendJson, but that would not follow the Open/Close principle. With this action we would had to change the original source code to add a new feature (should be open for extension, but closed for modification). How can we fix this code?

<?php
abstract class Type {}

class XML extends Type {}

class XML extends Json {}

class Webservice {

    public function send(Type $data) {
        // handle the send here
    }
}

To easily refactor this code accordingly with open/close principle we would first to create a new generic class called Type, this class will abstract the XMl and JSON type, which we can then give to the send date as a parameter.

<?php

$type = new Json();

$webService = new Webservice();
$webService->send($json);

This approach is much cleaner and easier to create a new extension to it, we can extend the Type class and create as many types as we want.

Liskov Substitution

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program [1] [2] [3].

This principle is connected with inheritance one of the most features used in the Object Oriented Programming. The principle says that the subtype should not alter the result of the program.

In the following example we have a class called Door, inside what we have is a simple method that tell us when the door is open or not.

<?php

class Door {

    protected $open = true;

    public function isOpen()
    {
        return $open;
    }

    public function open()
    {
        $this->open = true;
    }

    public function close()
    {
        $this->open = false;
    }
}

A simply class and easy to use, then we have our client code, who uses that class

<?php

class Client {

    public function execute(Door $door)
    {
        if ($door->isOpen()) {
            $door->close();
        }
    }
}

$client = new Client();
$client->execute(new Door());

Now let’ say we have other door but this time this door is a decorated one.

<?php

class DecoratedDoor extends Door {

    public function isOpen()
    {
        if (!parent::isOpen()) {
            throw new \Exception('Door is closed');
        }
    }
}

The code above is valid but if the client decided to use the DecoratedDoor his code will break. We are not following the Liskov principle “Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program”.

<?php

// Original code
$client = new Client();
$client->execute(new Door());

/**
 * The client can't use the DecoratedDoor because it throws an Exception
 */
$client = new Client();
$client->execute(new DecoratedDoor());

In other words to fix this problem and follow the Liskov principle we need just provide for the client class the same result that he expects from the Door class, true or false.

Interface Segregation

Many client-specific interfaces are better than one general-purpose interface [1] [2] [3].

This I think is the easier one to understand, the quote above is really clear. So let’ dive into the source code and get our hands dirty.

<?php
interface Bird {

    public function fly();

    public function eat();
}

The interface above has two methods to our Bird, and we can implement this interface really easy and well, as the code below shows:

<?php
class ConcreteBird implements Bird {

    public function fly()
    {
        print 'I can fly';
    }

    public function eat()
    {
        print 'I can eat';
    }
}

This kind of generalization works fine and there is just one problem with it, what if I have a bird who doesn’t fly or doesn’t eat?

We do not have many options though, we have to implement all methods, therefore what we can do to identify when a bird can’t fly or eat is thrown an exception.

<?php
class ConcreteBirdWithoutFly implements Bird {

    public function fly()
    {
        throw new \Exception('I cannot fly');
    }

    public function eat()
    {
        print 'I can eat';
    }
}

class ConcreteBirdWithoutEat implements Bird {

    public function fly()
    {
        print 'I can fly';
    }

    public function eat()
    {
        throw new \Exception('I cannot eat');
    }
}

For many of us its just ok to do this, but the true is that we are implementing a method that we don’t need. Then comes Interface Segregation to save us, “Many client-specific interfaces are better than one general-purpose interface”. To deal with that we simply create a new interface with the method eat.

<?php
interface Bird {

    public function fly();
}

interface Eatable {

    public function eat();
}

With this approach we can finally implements the specific interface instead of just throw exceptions if the class doesn’t support the contract. The following code supports both Bird and Eatable.

<?php

class ConcreteBirdWithoutEat implements Bird, Eatable {

    public function fly()
    {
        print 'I can fly';
    }

    public function eat()
    {
        print 'I can eat';
    }
}

This code is more flexible as well, we just implement what we are going to need.

Dependency inversion

One should “Depend upon Abstractions. Do not depend upon concretions” [1] [2] [3].

The following code just clarify the idea of abstraction and how to depend on it instead of a concrete instance.

<?php

abstract class Drivable {}

class Driver extends Drivable {}

class Car {

    public function run(Drivable $driver) {}
}

The following approach allow us to implement as many drivers as we want and give it as a parameter to the run method.

You can complain that we could easy depend directly on Driver, but if we do that we will block the flexibility of the code.

References

  1. [1]Wikipedia, “SOLID,” 2021 [Online]. Available at: https://en.wikipedia.org/wiki/SOLID_(object-oriented_design). [Accessed: 03-Apr-2021]
  2. [2]S. Oloruntoba, “SOLID: The First 5 Principles of Object Oriented Design,” 2020 [Online]. Available at: https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design. [Accessed: 17-Dec-2020]
  3. [3]D. Bailey, “S.O.L.I.D. Software Development, One Step at a Time,” 2020 [Online]. Available at: https://www.codemag.com/article/1001061. [Accessed: 28-Sep-2020]
  4. [4]M. Feathers, “the deep synergy between testability and good design,” 2013 [Online]. Available at: https://www.youtube.com/watch?v=4cVZvoFGJTU. [Accessed: 23-May-2021]