The 5 Principes of SOLID, easily explained using PHP

The 5 Principes of SOLID, easily explained using PHP Thumbnail image

The SOLID principles are 5 key principles when it comes to writing (and designing) object orientated programs. The original SOLID theory was introduced in 2000 by Robert C. Martin (although the actual mnemonic acronym 'SOLID' was introduced some years later) and has become a very popular set of principles to adhere to when programming.

The 5 principles of SOLID

Single responsibility principle

The single responsibility principle is a basic but very key concept. I think a lot of programmers somewhat instinctively do this without thinking about it - although once you are aware of this principle and think about it, you will probably end up refactoring some code.

The basic idea is that each class should do one thing, and one thing only.

The class should be provided with all the information or data that it needs, and it should do its one task and that alone.

  1. class BadlyDesignedBlogClass
  2. {
  3.  
  4.     public function blog_post($post_id)
  5.     {
  6.  
  7.         if (!\Auth::check() ) {
  8.             throw new \Exception("You are not logged in - all blog posts are for logged in users "); // not sure why, but for the purposes of this demo...
  9.         }
  10.  
  11.         $db = $this->connect_to_db();
  12.         $blog_post_sql = "select * from blog_posts where id = ?";
  13.  
  14.         $post = $this->run_db_query($db,$blog_post_sql, $post_id);
  15.        
  16.        
  17.         if (!empty($_POST['title'])) {
  18.             $this->edit_post($post,$_POST['title']);
  19.             echo "<h1>Saved edits!</h1>";
  20.             return true;
  21.         }
  22.  
  23.         echo "<h1>Viewing blog post: " . e($post->title) . "</h1>";
  24.         echo "<p>Maybe you want to edit it?</p>";
  25.         echo "<form>";
  26.         echo "<input type='text' name='title' value='" . e($post->title) . "'>";
  27.         echo "<input type=submit>";
  28.         echo "</form>";
  29.  
  30.         return true;
  31.  
  32.     }
  33.  
  34.     protected function edit_post(BlogPost $post, $new_title)
  35.     {
  36.         // save it ...
  37.     }
  38.     protected function connect_to_db()
  39.     {
  40.         // do the db connection here...
  41.     }
  42.     protected function run_db_query($db, $sql, $params)
  43.     {
  44.         // do the sql query here...
  45.     }
  46.  
  47. }

This class would be used like this, in a controller's method:

  1. class BlogController
  2. {
  3.     public function show($post_id)
  4.     {
  5.         $blog_post = new BadlyDesignedBlogClass();
  6.         $blog_post->view_and_edit_blog_post($post_id);
  7.     }
  8. }

Main problems with BadlyDesignedBlogClass():

  • It does far too much! It does the following things:
    • Checks if the user is logged in.
    • Creates a database connection
    • Queries the database itself
    • Maybe deals with updating the blog post (if $_POST was not empty)
    • Shows the blog post - it echos out HTML

Instead, the class(es) should have one single responsibility

So, time for a rewrite.

I would prefer to use a repository for the database connection and queries. The authentication should be in the controller (really, I'd put it in middleware or in some rules in the routes configuration, but I'm trying to keep this example simple without too many files/classes).

Here is a rewrite.

  1. class BlogController
  2. {
  3.     protected $blog_repo;
  4.  
  5.     public function __construct(BlogRepo $blog_repo)
  6.     {
  7.         $this->blog_repo = $blog_repo;
  8.     }
  9.  
  10.     public function show($post_id)
  11.     {
  12.         if (!\Auth::check()) {
  13.             // actually, I'd prefer to stick this 'behind' the controller, in some middleware for example if using Laravel, or some rules in the routing file. But this example is meant to be quite simple...
  14.             throw new \Exception("You are not logged in - all blog posts are for logged in users ");
  15.         }
  16.         //$blog_post = new BadlyDesignedBlogClass();
  17.         //$blog_post->view_and_edit_blog_post($post_id);
  18.  
  19.         $blog_post = $this->blog_repo->find($post_id);
  20.  
  21.         return view("show_blog_post.php", ['blog_post'=>$blog_post]);
  22.     }
  23.     public function update($post_id)
  24.     {
  25.  
  26.         $blog_post = $this->blog_repo->find($post_id);
  27.         $blog_post->update(['title'=>$_POST['title']);
  28.         // now it is saved, confirm that:
  29.         return view("updated_blog_post.php");
  30.  
  31.     }
  32. }
  33.  
  34.  
  35. class BlogRepo
  36. {
  37.     protected $connection;
  38.  
  39.     public function __construct($connection)
  40.     {
  41.         $this->connection = $connection;
  42.     }
  43.  
  44.     public function find($id)
  45.     {
  46.         // query the database for blog_posts with id = $id
  47.     }
  48. }

It is assumed that when BlogController is instantiated, a valid DBConnection gets sent to its constructor. It would be reasonable to just send a BlogRepository (which includes the DBConnection as one of its properties), but for the sake of example I just instantiated it in the controller's method.

The controller now has two methods. These methods get the requested blog post (via the blog repository class), then displays it (show()) or saves submitted changes (update() )

The old class had one method, that sent everything off to BadlyDesignedBlogClass which echoed out content

The new version is much cleaner, easier to test and easier to maintain.

(This example is simplified, I'm not really sure it is a great example to be honest. Maybe I'll rewrite it soon!)

a class should have only a single responsibility (i.e. changes to only one part of the software's specification should be able to affect the specification of the class).

Open/closed principle

The designer of the SOLID principles considered the 'Open/closed principle' the most important one, and it is hard to disagree with him. This principle says that classes (or functions, etc - but we would typically apply it to classes when dealing with PHP) should be open for extension, but closed for modification. This means that your code should be written so that others (or a future you) can modify how it works without having to edit the existing code. When talking about making a class open for extension, the obvious thing to mention is class inheritance and interfaces. The interfaces shouldn't change (closed for modification) - however of course the implementation of these publicly facing methods should be open for extension in subclasses.

Liskov substitution principle

The Liskov substitution principle (known as LSP) is an important concept when it comes to object oriented programming. It basically states that a certain bit of code (to make this example easier to explain, let's just say the 'client class') should be able to use either a base class, or any subclasses of that base class, and the 'client class' would not have to change any of it's own code or logic.

Another way of thinking of it: If you have a (base) class, and then you have five other classes that all extend that base class. Any code that uses the base class should also work if you replace that base class with any of it's subclasses.

See also: design by contract.

Interface segregation principle

The Interface segregation principle (known as ISP) is the principle that states that clients should not be forced to implement interfaces that they don't use. It states that it is better to have many 'thin' interfaces (remember, in PHP you can implement more than one interface in a class) than one huge 'fat' interface.

Dependency inversion principle

This is my favourite of the principles, as it produces much cleaner code which is easier to change at a later date and to test with.

I'll give an example with some PHP - If you have some client class that needs to talk to a database, you could type hint your class called MysqlDBConnection (and use a service container to provide the type hinted object). But then what if you change to SQLite? Do you make a class SQLiteDBConnection extends MysqlDBConnection? It is better if you type hinted DBConnectionInterface, and both of your Mysql/SQLite classes implemented these. Then leave it up to your application (for example, in a service container) to pass the correct object that implements it.

You can then provide different implementations, without having to change your main client code.

webdevetc profile pic
webdevetc

I am a 29 year old freelance backend web developer from London, mostly focusing on PHP and Laravel lately. This is my site - I mostly write about PHP here. Contact me here (especially for any contracting jobs early 2019 in London ;) ).

Leave a Comment