07 Feb 2021
cat: Laravel, PHP
0 Comments

Laravel 8 Clean Architecture Example

Introduction

Clean Architecture, like Onion Architecture is one of the popular domain centric architecture. You can read more about it here. This is my first time trying it on Laravel and I am basing my implementation from this C# example repository. The first thing I can notice is the app is not separated by components like modules with service classes, controllers, models, etc. Instead the app is separated by functions or features. In this tutorial we will just make a boilerplate Clean Architecture for Laravel 8 with examples on how to use it. Of course this can be done with other Laravel versions as well.

Directories and namespaces

We can first create the directories and namespaces. Since Laravel already uses the “App” namespace, we will use the “Core” as the “Application” layer. You can rename it however you like.

Normally in other languages and frameworks, these are separated by projects like in C# and Java and contained inside a solution. Then each project just add references when needed.

Laravel 8 Clean Architecture Example

- Common
- Core
- Domain
- Infrastructure
- Service

Update Composer

To get the namespaces working correctly we need to update the composer.json. We may need to run composer dump-autoload after updating it.

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Database\\Factories\\": "database/factories/",
        "Database\\Seeders\\": "database/seeders/",
        "Core\\": "Core/",
        "Domain\\": "Domain/",
        "Service\\": "Service/",
        "Infrastructure\\": "Infrastructure/",
        "Common\\": "Common/"
    }
},

Update phpunit.xml

We need to also update phpunit.xml if we want to put our test php classes in these new directories.

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
            <directory suffix="Test.php">./Core</directory>
            <directory suffix="Test.php">./Infrastructure</directory>
            <directory suffix="Test.php">./Service</directory>
            <directory suffix="Test.php">./Common</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </coverage>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <!-- <server name="DB_CONNECTION" value="sqlite"/> -->
        <!-- <server name="DB_DATABASE" value=":memory:"/> -->
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>

Domain Layer

The “Domain” directory is where I put the models.

Laravel 8 Clean Architecture Example

- Domain
      - Product
            - Product.php
      - User
            - User.php

Since the User model has been moved, config for the authentication should be updated as well. In case of a fresh Laravel Project with Jetstream and Passport, make sure to check and update these files.

  • Actions/Fortify/CreateNewUser.php
  • config/auth.php
  • database/factories/UserFactory.php
  • database/seeders/DatabaseSeeder.php

Application Layer

Currently the “Core” directory is the application layer. Here I put the CQRS which can be used by the presentation layer (laravel controllers and views) and service layer. In the image below, there is a command and query examples.
We can also put the DTOs, View-models, mappers, exceptions, interfaces and maybe some validation and logic in this layer.

Laravel 8 Clean Architecture Example

- Core
      - Interfaces
            - IImageOptimizeService.php
      - Product
            - Commands
                  - CreateProduct.php
                  - CreateProductModel.php
                  - CreateProductTest.php
                  - ICreateProduct
            - Queries
                  - GetProductPagination.php
                  - GetProductPaginationFilter.php
                  - GetProductPaginationTest.php
                  - IGetProductPagination.php
            - Repositories
                  - IProductRepository.php
      - Repository
            - ICriteria.php
            - IRepository.php

I also implemented the repository pattern to take out the database logic out of the Application layer. The Application layer will just reference the interfaces and the implementation will be inside the Infrastructure layer.

Persistence Layer

I did not include the persistence layer since Laravel already has database migrations built in. If you would like to have a separate “Persistence” directory you will have to specify the migration path when running the migrations or set the migration path in AppServiceProvider using $this->loadMigrationsFrom('path');.

Infrastructure Layer

In this layer we can put the implementations for the interfaces from the Application layer and web-services. We can put other stuff here as well but Laravel offers a lot of features like logging, caching, queued-jobs, notifications, etc. Maybe if you like, you can abstract some Laravel features and make interfaces in Application layer then implement them in Infrastructure layer. That way, the Application layer don’t have to care how these features are implemented.

Laravel 8 Clean Architecture

- Infrastructure
      - ImageOptimize
            - ImageOptimizeService.php
            - ImageOptimizeServiceTest.php
      - Product
            - NameCriteria.php
            - OrderByLatest.php
            - ProductRepository.php
            - ProductRepositoryTest.php
      - Repository
            - Repository.php -> Base repository

Common Layer

In this layer we can put utility classes that are common throughout the app. Though Laravel already have Facades like Date, Str, Arr, etc. Another example of a utility class would be mobile number or money formatter. I just created a sample DateService for demo purposes.

- Common
    - Date
          - DateHelper.php -> Just a facade for ease of use
          - DateService.php
          - DateServiceTest.php
          - IDateService.php

Service Layer

Since I am basing this project from this clean architecture example, I also implemented this layer. We can create the REST API here that client facing apps can use or other API consumer like a separate system.

Laravel 8 Clean Architecture

- Service
      - Common
            - Controller.php
            - Trasformer.php
      - Product
            - CreateProductRequest.php
            - GetProductPaginationRequest.php
            - ProductController.php
            - ProductControllerTest.php
            - ProductTransformer.php

Presentation Layer

To not break too much of the Laravel setup, the presentation layer will be just the normal Laravel repository where controllers and views stays on their respective location instead of a new directory called “Presentation”. Here is an example of how I created a new page while still trying to implement functional cohesion.

Laravel 8 Clean Architecture

- app
      - Http
            - Controllers
            - Middleware
            - Page -> Misc. pages like welcome page.
            - Product
                  - CreateProductRequest.php
                  - GetProductPaginationRequest.php
                  - ProductController.php

Comments and suggestions are greatly appreciated. That is all for now. Thanks for reading!

References

Be the first to write a comment.

Post A Comment