PHP
Article

Clean Code Architecture and Test Driven Development in PHP

By Vitalij Mik

The Clean Code Architecture was introduced by Robert C. Martin on the 8light blog. The idea was to create an architecture which is independent of any external agency. Your business logic should not be coupled to a framework, a database, or to the web itself. With the independence, you have several advantages. For example, you have the ability to defer technical decisions to a later point during development (e.g. choosing a framework and choosing a database engine/provider). You can also easily switch the implementations or compare different implementations, but the biggest advantage is that your tests will run fast.

Just think about it. Do you really have to run through a router, load a database abstract layer or some ORM magic, or execute some other code just to assert one or more results?

I started to learn and practice this architecture because of my old favorite framework Kohana. At some point, the core developer stopped maintaining the code, which also meant that my projects would not get any further updates or security fixes. This meant that I had to either move to another framework and rewrite the entire project or trust the community development version.

Clean Code Architecture

I could have chosen to go with another framework. Maybe it would have been better to go with Symfony 1 or Zend 1, but by now that framework would have also changed.

Frameworks will continue to change and evolve. With composer, it is easy to install and replace packages, but it is also easy to abandon a package (composer even has the option to mark a package as abandoned), so it is easy to make “the wrong choice”.

In this tutorial, I will show you how we can implement the Clean Code Architecture in PHP, in order to be in control of our own logic, without being dependent on external providers, but while still using them. We will create a simple guestbook application.


The image above shows the different layers of the application. The inner layers do not know anything about the outer layers and they all communicate via interfaces.

The most interesting part is in the bottom-right corner of the image: Flow of control. The image explains how the framework communicates with our business logic. The Controller passes its data to the input port, which is processed by an interactor to produce an output port which contains data for the presenter.

We will start with the UseCase layer, since this is the layer which contains our application-specific-logic. The Controller layer and other outer layers belong to a Framework.

Note that all the various stages described below can be cloned and tested from this repo, which was neatly arranged into steps with the help of Git tags. Just download the corresponding step if you’d like to see it in action.

First test

We usually begin from the UI point of view. What should we expect to see if we visit a guestbook? There should be some kind of input form, the entries from other visitors, and maybe a navigation panel to search through pages of entries. If the guestbook is empty, we might see a message like “No entries found”.

In our first test we want to assert an empty list of entries, it looks like this:

<?php
require_once __DIR__ . '/../../vendor/autoload.php';
class ListEntriesTest extends PHPUnit_Framework_TestCase
{
    public function testEntriesNotExists()
    {
        $request = new FakeViewEntriesRequest();
        $response = new FakeViewEntriesResponse();
        $useCase = new ViewEntriesUseCase();
        $useCase->process($request, $response);
        $this->assertEmpty($response->entries);
    }
}

In this test, I used a slightly different notation than Uncle Bob. The Interactors are UseCases, Input Ports are Requests, and Output Ports are Responses.

The UseCases always contain the method process which has a type hint to its specific Request and Response interface.

According to the Red, Green, and Refactor cycles in TDD, this test should and will fail, because the classes do not exist.

After creating the class files, methods, and properties, the test passes.
Since the classes are empty, we do not need to use the Refactor cycle at this point.

Next, we want to assert that we can actually see some entries.

<?php
require_once __DIR__ . '/../../vendor/autoload.php';
use BlackScorp\GuestBook\Fake\Request\FakeViewEntriesRequest;
use BlackScorp\GuestBook\Fake\Response\FakeViewEntriesResponse;
use BlackScorp\GuestBook\UseCase\ViewEntriesUseCase;
class ListEntriesTest extends PHPUnit_Framework_TestCase
{
    public function testEntriesNotExists()
    {
        $request = new FakeViewEntriesRequest();
        $response = new FakeViewEntriesResponse();
        $useCase = new ViewEntriesUseCase();
        $useCase->process($request, $response);
        $this->assertEmpty($response->entries);
    }
    public function testCanSeeEntries()
    {
        $request = new FakeViewEntriesRequest();
        $response = new FakeViewEntriesResponse();
        $useCase = new ViewEntriesUseCase();
        $useCase->process($request, $response);
        $this->assertNotEmpty($response->entries);
    }
}

As we can see, the test fails, and we are in the red section of the TDD cycle. To make the test pass we have to add some logic into our UseCases.

Sketch out the UseCase logic

Before we start with the logic, we apply the parameter type hints and create the interfaces.

<?php
namespace BlackScorp\GuestBook\UseCase;

use BlackScorp\GuestBook\Request\ViewEntriesRequest;
use BlackScorp\GuestBook\Response\ViewEntriesResponse;

class ViewEntriesUseCase
{
    public function process(ViewEntriesRequest $request, ViewEntriesResponse $response){

    }
}

This is similar to how graphic artists often work. Instead of drawing the entire picture from beginning to end, they usually draw some shapes and lines to have an idea of what the finished picture might be. Afterwards, they use the shapes as guides and add more details. This process is called “Sketching”.

Instead of shapes and lines, we use, for example, Repositories and Factories as our guides.

The Repository is an abstract layer for retrieving data from a storage. The storage could be a database, it could be a file, an external API or even in memory.

To view the guestbook entries, we have to find the entities in our repository, convert them to views, and add them to the response.

<?php
namespace BlackScorp\GuestBook\UseCase;

use BlackScorp\GuestBook\Request\ViewEntriesRequest;
use BlackScorp\GuestBook\Response\ViewEntriesResponse;

class ViewEntriesUseCase
{
    public function process(ViewEntriesRequest $request, ViewEntriesResponse $response){
		$entries = $this->entryRepository->findAllPaginated($request->getOffset(), $request->getLimit());

        if(!$entries){
            return;
        }

        foreach($entries as $entry){
            $entryView = $this->entryViewFactory->create($entry);
            $response->addEntry($entryView);
        }
    }
}

You might ask, why do we convert the entry Entity to a View?

The reason is that the Entity should not go outside the UseCases layer. We can only find an Entity with the help of the repository, so we modify/copy it if necessary and then put it back into the repository (when modified).

When we begin to move the Entity into the outer layer, it is best to add some additional methods for communication purposes, but the Entity should only contain core business logic.

As we are not sure of how we want to format the entries, we can defer this step.

Another question might be “Why a factory?”

If we create a new instance inside the loop such as

$entryView = new EntryView($entry);
$response->addEntry($entryView);

we would violate the dependency inversion principle. If, later on, we require another view object in the same UseCase logic, we would have to change the code. With the factory, we have an easy way to implement different views, which might contain different formatting logic, while still using the same UseCase.

Implementing external dependencies

At this point, we already know the dependencies of the UseCase: $entryViewFactory and $entryRepository. We also know the methods of the dependencies. The EntryViewFactory has a create method which accepts the EntryEntity, and the EntryRepository has a findAll() method which returns an array of EntryEntities. Now we can create the interfaces with the methods and apply them to the UseCase.

The EntryRepository will looks like this:

<?php
namespace BlackScorp\GuestBook\Repository;

interface EntryRepository {
    public function findAllPaginated($offset,$limit);
}

And the UseCase like so

<?php
namespace BlackScorp\GuestBook\UseCase;

use BlackScorp\GuestBook\Repository\EntryRepository;
use BlackScorp\GuestBook\Request\ViewEntriesRequest;
use BlackScorp\GuestBook\Response\ViewEntriesResponse;
use BlackScorp\GuestBook\ViewFactory\EntryViewFactory;

class ViewEntriesUseCase
{
    /**
     * @var EntryRepository
     */
    private $entryRepository;
    /**
     * @var EntryViewFactory
     */
    private $entryViewFactory;

    /**
     * ViewEntriesUseCase constructor.
     * @param EntryRepository $entryRepository
     * @param EntryViewFactory $entryViewFactory
     */
    public function __construct(EntryRepository $entryRepository, EntryViewFactory $entryViewFactory)
    {
        $this->entryRepository = $entryRepository;
        $this->entryViewFactory = $entryViewFactory;
    }


    public function process(ViewEntriesRequest $request, ViewEntriesResponse $response)
    {
        $entries = $this->entryRepository->findAllPaginated($request->getOffset(), $request->getLimit());
        if (!$entries) {
            return;
        }

        foreach ($entries as $entry) {
            $entryView = $this->entryViewFactory->create($entry);
            $response->addEntry($entryView);
        }
    }
}

As you can see, the tests still fail because of the missing dependency implementation. So here we just create some fake objects.

<?php
require_once __DIR__ . '/../../vendor/autoload.php';
use BlackScorp\GuestBook\Fake\Request\FakeViewEntriesRequest;
use BlackScorp\GuestBook\Fake\Response\FakeViewEntriesResponse;
use BlackScorp\GuestBook\UseCase\ViewEntriesUseCase;
use BlackScorp\GuestBook\Entity\EntryEntity;
use BlackScorp\GuestBook\Fake\Repository\FakeEntryRepository;
use BlackScorp\GuestBook\Fake\ViewFactory\FakeEntryViewFactory;
class ListEntriesTest extends PHPUnit_Framework_TestCase
{
    public function testEntriesNotExists()
    {
        $entryRepository = new FakeEntryRepository();
        $entryViewFactory = new FakeEntryViewFactory();
        $request = new FakeViewEntriesRequest();
        $response = new FakeViewEntriesResponse();
        $useCase = new ViewEntriesUseCase($entryRepository, $entryViewFactory);
        $useCase->process($request, $response);
        $this->assertEmpty($response->entries);
    }
    public function testCanSeeEntries()
    {
		$entities = [];
        $entities[] = new EntryEntity();
        $entryRepository = new FakeEntryRepository($entities);
        $entryViewFactory = new FakeEntryViewFactory();
        $request = new FakeViewEntriesRequest();
        $response = new FakeViewEntriesResponse();
        $useCase = new ViewEntriesUseCase($entryRepository, $entryViewFactory);
        $useCase->process($request, $response);
        $this->assertNotEmpty($response->entries);
    }
}

Because we already created the interfaces for the repository and view factory, we can implement them in the fake classes, and also implement the request/response interfaces.

The repository now looks like this:

<?php
namespace BlackScorp\GuestBook\Fake\Repository;

use BlackScorp\GuestBook\Repository\EntryRepository;

class FakeEntryRepository implements EntryRepository
{
    private $entries = [];

    public function __construct(array $entries = [])
    {
        $this->entries = $entries;
    }

    public function findAllPaginated($offset, $limit)
    {
        return array_splice($this->entries, $offset, $limit);
    }
}

and the view factory like this:

<?php
namespace BlackScorp\GuestBook\Fake\ViewFactory;

use BlackScorp\GuestBook\Entity\EntryEntity;
use BlackScorp\GuestBook\Fake\View\FakeEntryView;
use BlackScorp\GuestBook\View\EntryView;
use BlackScorp\GuestBook\ViewFactory\EntryViewFactory;

class FakeEntryViewFactory implements EntryViewFactory
{
    /**
     * @param EntryEntity $entity
     * @return EntryView
     */
    public function create(EntryEntity $entity)
    {

        $view = new FakeEntryView();
        $view->author = $entity->getAuthor();
        $view->text = $entity->getText();
        return $view;
    }
}

You may wonder, why don’t we just use mocking frameworks to create the dependencies? There are two reasons:

  1. Because it is easy to create an actual class with the editor. So there is no need to use them.
  2. When we start to create the implementation for the framework, we can use these fake classes in the DI Container and fiddle with the template without having to make a real implementation.

The tests now pass, and we can go to refactoring. There is in fact nothing that can be refactored in the UseCase class, only in the test class.

Refactoring the test

The execution is actually the same, we just have a different setup and assertion. We can thus extract the initialization of the fake classes and processing the UseCase to a private function processUseCase.

The test class should now look like this

<?php
require_once __DIR__ . '/../../vendor/autoload.php';

use BlackScorp\GuestBook\Entity\EntryEntity;
use BlackScorp\GuestBook\Fake\Repository\FakeEntryRepository;
use BlackScorp\GuestBook\Fake\ViewFactory\FakeEntryViewFactory;
use BlackScorp\GuestBook\Fake\Request\FakeViewEntriesRequest;
use BlackScorp\GuestBook\Fake\Response\FakeViewEntriesResponse;
use BlackScorp\GuestBook\UseCase\ViewEntriesUseCase;

class ListEntriesTest extends PHPUnit_Framework_TestCase
{

    public function testCanSeeEntries()
    {
 		$entries = [
            new EntryEntity('testAuthor','test text')
        ];
        $response = $this->processUseCase($entries);
        $this->assertNotEmpty($response->entries);
    }

    public function testEntriesNotExists()
    {
        $entities = [];
        $response = $this->processUseCase($entities);
        $this->assertEmpty($response->entries);
    }

    /**
     * @param $entities
     * @return FakeViewEntriesResponse
     */
    private function processUseCase($entities)
    {
        $entryRepository = new FakeEntryRepository($entities);
        $entryViewFactory = new FakeEntryViewFactory();
        $request = new FakeViewEntriesRequest();
        $response = new FakeViewEntriesResponse();
        $useCase = new ViewEntriesUseCase($entryRepository, $entryViewFactory);
        $useCase->process($request, $response);
        return $response;
    }
}

Independence

Now, for example, we can easily create new test cases with invalid entities, and we can move the repository and factory to the setup method and run the tests with real implementations.

As you can see, we can implement a ready to use UseCase into the DI Container and use it inside any framework. The logic is framework agnostic.

We could create another repository implementation which speaks to an external API, for example, and pass it to the UseCase. The logic is database agnostic.

We could create CLI request/response objects and pass them to the same UseCase which is used inside a controller, so the logic is independent of the platform.

We could even execute different UseCases in a row where every UseCase might modify the actual response object.

class MainController extends BaseController
{
    public function indexAction(Request $httpRequest)
    {
        $indexActionRequest = new IndexActionRequest($httpRequest);
        $indexActionResponse = new IndexActionResponse();
        $this->getContainer('ViewNavigation')->process($indexActionRequest, $indexActionResponse);
        $this->getContainer('ViewNewsEntries')->process($indexActionRequest, $indexActionResponse);
        $this->getContainer('ViewUserAvatar')->process($indexActionRequest, $indexActionResponse);
        $this->render($indexActionResponse);

    }
}

Pagination

Now we want to add pagination. The test may look like this:

   public function testCanSeeFiveEntries(){
        $entities = [];
        for($i = 0;$i<10;$i++){
             $entities[] = new EntryEntity('Author '.$i,'Text '.$i);
        }

        $response = $this->processUseCase($entities);
        $this->assertNotEmpty($response->entries);
        $this->assertSame(5,count($response->entries));
    }

This test will fail, so we have to modify the process method of the UseCase and also rename the method findAll to findAllPaginated.

 public function process(ViewEntriesRequest $request, ViewEntriesResponse $response){
        $entries = $this->entryRepository->findAllPaginated($request->getOffset(), $request->getLimit());
//....
    }

Now we apply the new parameters to the interface and the fake repository and add the new methods to the request interface.

The repository’s findAllPaginated method changes a little:

    public function findAllPaginated($offset, $limit)
    {
        return array_splice($this->entries, $offset, $limit);
    }

and we have to move the request object in the tests; also, the limit parameter will be required in the constructor of our request object. This way, we will force the setup to create the limit with a new instance.

  public function testCanSeeFiveEntries(){
        $entities = [];
        for($i = 0;$i<10;$i++){
            $entities[] = new EntryEntity();
        }
        $request = new FakeViewEntriesRequest(5);
        $response = $this->processUseCase($request, $entities);
        $this->assertNotEmpty($response->entries);
        $this->assertSame(5,count($response->entries));
    }

The test passes, but we have to test if we can see the next five entries. Therefore, we have to extend the request object with a setPage method.

<?php
namespace BlackScorp\GuestBook\Fake\Request;
use BlackScorp\GuestBook\Request\ViewEntriesRequest;
class FakeViewEntriesRequest implements ViewEntriesRequest{
    private $offset = 0;
    private $limit = 0;

    /**
     * FakeViewEntriesRequest constructor.
     * @param int $limit
     */
    public function __construct($limit)
    {
        $this->limit = $limit;
    }

    public function setPage($page = 1){
        $this->offset = ($page-1) * $this->limit;
    }
    public function getOffset()
    {
        return $this->offset;
    }

    public function getLimit()
    {
        return $this->limit;
    }

}

With this method, we can test if we can see the next five entries.

   public function testCanSeeFiveEntriesOnSecondPage(){
        $entities = [];
        $expectedEntries = [];
        $entryViewFactory = new FakeEntryViewFactory();
        for($i = 0;$i<10;$i++){
            $entryEntity = new EntryEntity();
            if($i >= 5){
                $expectedEntries[]=$entryViewFactory->create($entryEntity);
            }
            $entities[] =$entryEntity;
        }
        $request = new FakeViewEntriesRequest(5);
        $request->setPage(2);
        $response = $this->processUseCase($request,$entities);
        $this->assertNotEmpty($response->entries);
        $this->assertSame(5,count($response->entries));
        $this->assertEquals($expectedEntries,$response->entries);
    }

Now the tests pass and we can refactor. We move the FakeEntryViewFactory to the setup method and we are done with this feature. The final test class looks like this:

<?php
require_once __DIR__ . '/../../vendor/autoload.php';

use BlackScorp\GuestBook\Entity\EntryEntity;
use BlackScorp\GuestBook\Fake\Repository\FakeEntryRepository;
use BlackScorp\GuestBook\Fake\Request\FakeViewEntriesRequest;
use BlackScorp\GuestBook\Fake\Response\FakeViewEntriesResponse;
use BlackScorp\GuestBook\Fake\ViewFactory\FakeEntryViewFactory;
use BlackScorp\GuestBook\UseCase\ViewEntriesUseCase;

class ListEntriesTest extends PHPUnit_Framework_TestCase
{
    public function testEntriesNotExists()
    {
        $entries = [];
        $request = new FakeViewEntriesRequest(5);
        $response = $this->processUseCase($request, $entries);
        $this->assertEmpty($response->entries);
    }

    public function testCanSeeEntries()
    {
        $entries = [
            new EntryEntity('testAuthor', 'test text')
        ];
        $request = new FakeViewEntriesRequest(5);
        $response = $this->processUseCase($request, $entries);
        $this->assertNotEmpty($response->entries);
    }

    public function testCanSeeFiveEntries()
    {
        $entities = [];
        for ($i = 0; $i < 10; $i++) {
            $entities[] = new EntryEntity('Author ' . $i, 'Text ' . $i);
        }
        $request = new FakeViewEntriesRequest(5);
        $response = $this->processUseCase($request, $entities);
        $this->assertNotEmpty($response->entries);
        $this->assertSame(5, count($response->entries));
    }

    public function testCanSeeFiveEntriesOnSecondPage()
    {
        $entities = [];
        $expectedEntries = [];
        $entryViewFactory = new FakeEntryViewFactory();
        for ($i = 0; $i < 10; $i++) {
            $entryEntity = new EntryEntity('Author ' . $i, 'Text ' . $i);
            if ($i >= 5) {
                $expectedEntries[] = $entryViewFactory->create($entryEntity);
            }
            $entities[] = $entryEntity;
        }
        $request = new FakeViewEntriesRequest(5);
        $request->setPage(2);
        $response = $this->processUseCase($request, $entities);
        $this->assertNotEmpty($response->entries);
        $this->assertSame(5, count($response->entries));
        $this->assertEquals($expectedEntries, $response->entries);
    }

    /**
     * @param $request
     * @param $entries
     * @return FakeViewEntriesResponse
     */
    private function processUseCase($request, $entries)
    {
        $repository = new FakeEntryRepository($entries);
        $factory = new FakeEntryViewFactory();
        $response = new FakeViewEntriesResponse();
        $useCase = new ViewEntriesUseCase($repository, $factory);
        $useCase->process($request, $response);
        return $response;
    }
}

Conclusion

In this tutorial, we have seen how the test leads us to the UseCase, which leads us to interfaces which, in turn, lead us to fake implementations.

Again, the source code for this article can be found on Github – check the tags for all the various source code stages of this post.

This tutorial demonstrates that it is not that difficult to apply Test Driven Development and Clean Code Architecture to any newly created project. The great advantage is that while the logic is completely independent, we can still use third-party libraries.

Questions? Comments? Please leave them in the comment section right below the like button!

Vitalij Mik
Meet the author
German PHP developer since 2011
  • Martin Danielsson

    Pretty neat :) You have a typo in the section “Refactoring the Test”. Your first test will fail because you have $entries vs $entities!


    public function testCanSeeEntries()
    {
    $entries = [
    new EntryEntity('testAuthor','test text')
    ];

    $response = $this->processUseCase($entities);
    $this->assertNotEmpty($response->entries);
    }

    • http://www.bitfalls.com/ Bruno Skvorc

      Thanks, fixed!

  • Fabio Mattei

    Nice article, well written!
    Your interpretation of Robert’s architecture is interesting!
    I am wondering if, given the capability of PHP of creating stdClass, you really need a entryViewFactory to create an EntryView to pass back to the View.
    Robert describes that process, but he is using Java, a statically typed language. I’m wondering if, in PHP, it would be better to keep things simple.

    • František Maša

      IMHO specific class is always better. There are several reasons:
      – IDE support for getters
      – you may add custom methods when needed
      – typehinting and polymorphism

      The only change I’d would ve making EntryView immutable (because it’s in fact DTO)

      • Witali Mik

        True, it is very handy to work with the IDE if the IDE knows the methods, also you can add some specific methods(which formats things for example) but i tried to make the things simple and clean in the Article, so a basic class with public properties were good enough for this

        • Fabio Mattei

          I understand your point of view.
          And I think you are right, because Rasmus Lerdorf too, with PHP 7, is going to a direction of a more strictly typed language.
          Anyway, as ex Java developer, I cannot stop thinking that even if the code is generated by the IDE, that code is noise to my eyes when I am reading it in order to make some change.
          The less code you have the less efforts you need to make to work with it. Too much code tends to hide the meaning of the code.

  • http://arielsoftwares.com Mark Wahlberg

    Great!! Thanks for the such a wonderful explanation.

  • Witali Mik

    Indeed, as Uncle Bob told on his 8th light blog, Hexagonal Architecture,Onion Architecture and others are very similar because all of them have the same Goal, the Clean Code Architecture is actually the same with different notations

  • janetrimmer

    Can you please go back several steps? Where do you put this code? How do you run it?

  • Manny Salom

    Thanks so much fro this awesome tutorial!

  • Fabian Schweizer

    Let’s say I have a use case which returns an error if a repository is empty and if not it returns the collection from the repo. Therefore I need to test with two different “repositories” (or at least with two different returns from it).
    Seeing your examples I would guess you would write a FakeRepository and inject the collection by the test?
    Why not using mocking from phpunit instead? Can you elaborate on that?

    • Witali Mik

      Well, instead of reusing the Fake Repository, i could also just create a new Repository like something “EmptyFakeRepository” and this repository might already have the collection, i just showed in this example that there is a way to create a Fake Repo and fill it inside test.

      I described before that i prefer to create a simple class, one reason is the execution time(iam sure that there is some logic which has to be executed to mock a class on the fly), another is that you make your test dependant on the mocking framework and the main reason is, i can use those fake classes to fiddle around in the HTML code or JavaScript, just inject fake repositories into project and you have “Content First” approach. You can create full functionality of a project without real implementation for the styling purpose or demonstration.

      Also if i would use them in this example, i had to explain what it is, link to the mocking framework and at least mention that there are more mocking frameworks around.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.