Clean Architecture with PHP

George
Technical blog from UNIL engineering teams
8 min readMar 1, 2023
Photo by Ben Griffiths on Unsplash

In this post we take a look at an example of a PHP application which adheres to the principles of Clean Architecture. The example application does not rely on any PHP framework (MVC). The full source code for the application is available on GitHub.

Copyright disclaimer

The example application examined here was inspired heavily by the article by Kevin Smith: “Modern PHP Without a Framework” and the accompanying code.

Why no MVC framework?

In his excellent article, Kevin Smith goes into details of how a “modern” PHP application can be developed without using a framework. I encourage the reader to consult his article and I would like to join a couple of remarks to his observations.

When it comes to designing an application following Clean Architecture (CA) principles, I found that many popular PHP frameworks bring in too much complexity and obfuscate some important design considerations. First, there is a primordial separation of “core” and “infrastructure”: a cornerstone idea of CA, which gets lost in a midst of all the scaffolding required by the framework. Then, there is an issue of separating Controller and Presenter, in a typical control flow in CA, which is difficult to adhere to when using an MVC framework.

This being said, there are some excellent examples of PHP applications following Clean Architecture or Hexagonal Architecture (or some variant of these) and which take advantage of frameworks. For instance, here is such an application by Herberto Graça.

Bootstrapping PHP libraries

So I’ve followed closely the implementation and the choice of libraries given by Kevin Smith to bootstrap my application. Here are the important building blocks of the application.

Together these libraries create a canvas for a simple, modular, and extensible PHP application. All of bootstrapping and configuration takes place in the application main class App.php . We now can focus on the architectural concepts proper.

Example application, domain

The application itself is a Hello World application which displays a greeting message to the user. There are, however, some additional features:

  • a random greeting is displayed to the user on demand
  • each greeting is associated with an author
  • all greetings are stored in a local database
  • an authenticated user can change the author of a greeting

The domain model used by the application is very simple — it consists of a single aggregate: Greeting . Each greeting contains a text message and two Value Objects: GreetingId wrapping an identifier for the greeting instance and Author — encapsulating author’s name.

Repository

Repository pattern is used to provide persistence of Greeting aggregate instance. A secondary adapter, FilePersistenceGateway , uses domain model to associative array mapper, implemented by PerstenceMapper , to map domain objects to the data structures persisted and/or queried by SleekDB store. Using Repository pattern guarantees that the persistence implementation is independent from any particular database. Use cases will only ever deal with PersistenceGatewayOperationsOutputPort interface (output port) for all their persistence operations.

DI container, controllers

Using Relay middleware together with request dispatcher and ContainerInterface implementation by PHP-DI, gives us an elegant and transparent way to wire any dependencies into our Controllers and Use Cases. Each controller will receive a fully autowired instance of a particular use case. Each use case instance will be autowired with an instance of required presenter and any required instances of output ports allowing the use case to perform its business logic and communicate results back to the user. This design follows closely the typical control flow in Clean Architecture which dictates a thorough separation of Controllers and Presenters. Here is how a typical use case looks like:

class WelcomeController
{

private WelcomeInputPort $useCase;

/**
* @param WelcomeInputPort $useCase
*/
public function __construct(WelcomeInputPort $useCase)
{
$this->useCase = $useCase;
}

public function __invoke(): void
{
$this->useCase->welcome();
}
}

Notice how Controller just executes the use case, without worrying itself how and when to process the result of the use case’s execution.

Presenters

Again, following closely Kevin Smith’s suggestion, presentation is handled via direct call to PHP Server API. An instance of SapiResponseEmitter (wrapping an instance of Narrowspark’s SAPI emitter) is injected into each presenter together with HTTP response. Each presenter is also injected with a template processor based on Twig engine. This way the application can take advantage of a powerful template processing, which greatly facilitates view creation. Most of the code for presentation is shared by all presenters and resides in an abstract class shown below.

abstract class AbstractWebPresenter implements ErrorHandlingPresenterOutputPort
{
// PSR-7 HTTP response
protected ResponseInterface $response;

// SAPI emitter
protected ResponseEmitter $responseEmitter;

// Template engine
protected TemplatesProcessor $templatesProcessor;

// some code omitted

protected function presentTemplatedResponse(string $template, $args = [], int $status = 200,
string $contentType = 'text/html'): void
{
$response = $this->response->withHeader('Content-Type', $contentType)
->withStatus($status);

$body = $this->templatesProcessor->processTemplate($template, $args);

$response->getBody()->write($body);
$this->responseEmitter->emit($response);
}

}

Core, infrastructure, ports

Here we get to the most relevant part of our article. The application is structured so as to follow to the letter the paradigm and nomenclature of Clean Architecture and Hexagonal Architecture. First and foremost, core classes of the application (inside of the hexagon) are clearly separated from the infrastructure code (outside of the hexagon).

Core and infrastructure of the hexagon are clearly separated

Now, let’s examine ports part of the core more closely.

Input, output ports and Presenter interfaces

Ports (or “Boundaries” how Robert C. Martin calls them) are interfaces through which the outside of the hexagon communicates with the inside. They are of two kinds: Input Ports — interfaces which Use Cases implement and which are used by Primary Adapters, and Output Ports — interfaces implemented by Secondary Adapters. Technically speaking, interfaces for Presenters are also part of Output Ports, but I prefer to put them into a separate package to highlight their symmetry with Input Ports: each category will group interfaces by use case of the application (welcome, login, etc.), whether as, Output Ports for secondary adapters on the righthand-side of the hexagon are grouped by the underlying technology aspect (configuration, DB, etc.).

Use cases, Ubiquitous Language

What differentiates Clean Architecture from Hexagonal Architecture, in my opinion, is the fact that it is use case centric. The central and most important aspect of the design are the use cases. Use cases implement specific, concrete business scenarios, most often associated with a specific user. Use Cases must follow closely Ubiquitous Language of the application domain — in particular, they must be named as to clearly and unambiguously reflect the exact business scenario at hand. In our example application we have defined four such scenarios:

  • As an anonymous or an authenticated user, I can visit a welcome screen of the application presenting a top-level menu.
  • As an anonymous or an authenticated user, I can prompt the system to “say hello” to me with a random greeting message.
  • As an anonymous user, I can login to the system using pre-configured credentials.
  • As an authenticated user, I can update the author of any greeting in the system.

Notice how the packaging and naming of the components in the Use Case layer reflet the Ubiquitous Language for our domain:

Use Cases follow closely Ubiquitous Language

Use case, flow of control

Here is how we implement the use case for updating a greeting’s author:

class UpdateAuthorUseCase implements UpdateAuthorInputPort
{

// output port for presenter
private UpdateAuthorPresenterOutputPort $presenter;

// output port for security adapter
private SecurityOperationsOutputPort $securityOps;

// output port for persistence gateway
private PersistenceGatewayOperationsOutputPort $gatewayOps;

// some code omitted

public function updateAuthorOfGreeting(string|int $greetingId, string $updatedAuthorName): void
{

try {

// security check
$this->securityOps->assertUserIsLoggedIn();

// construct GreetingID value object
$greetingId = GreetingId::of($greetingId);

// find the greeting
$greeting = $this->gatewayOps->obtainGreetingById($greetingId);

// make new (updated) greeting
$updatedGreeting = $greeting->withAuthorName($updatedAuthorName);

// persist updated greeting
$this->gatewayOps->updateGreeting($updatedGreeting);
} catch (UserNotAuthenticatedError|TypeError|InvalidDomainObjectError|GreetingPersistenceError $e) {

// present erroneous outcome
$this->presenter->presentError($e);
return;
}

// present successful outcome
$this->presenter->presentAuthorUpdatedSuccessfully($greetingId);
}
}

The points of interest are:

  • There are absolutely no dependencies outside of the core. Use Cases deal exclusively with domain entities, value objects and Ports.
  • Security (checking that the user is authenticated) is of a concern to the use case and is called from the use case. See this article for a more thorough discussion of security in Clean Architecture.
  • A special care has been taken to catch all business exceptions which can arise during the normal execution of the use case and to handle them in a separate control flow (in this case: simply calling the presenter with an erroneous outcome). See this article discussing the flow of control in a use case in more details.

Testing and analysis

One of the defining characteristics of an application following Clean Architecture is that is very simple to test Use Cases. In our application, is done in SayHelloUseCaseTest.php as an illustration using PHPUnit and Mockery.

There is a wonderful project: php-clean-architecture by Valeriy Chetkov available for PHP developers. It can perform several checks and provide several metrics which can be very helpful when analyzing architecture aspects of a PHP application. I strongly encourage reader to take advantage of this tool: it is very flexible and easy to use. The tool reports a wealth of information and metrics. Below, there is just one example of a dependencies diagram which can be generated for an application. It shows, for our implementation, that all dependencies are following the allowed direction (“outside-in”): from the infrastructure to the core.

Dependency graph generated with php-clean-architecture tool by Valeriy Chetkov

Discussion

In this article we look at the way we can design and implement PHP applications applying Clean Architecture principles. The example application illustrates the most relevant design considerations:

  • domain modeling with DDD
  • Repository pattern
  • Hexagonal Architecture with core/infrastructure separation
  • Clean Architecture with Use Case driven development

Thank you for your attention.

References

--

--