An alternative way to organize the Laravel directory structure

Published on June 10th, 2019

In this article, I would like to show you an alternative way to organize your Laravel directory structure. I think the default structure is fine for the most projects. But when it comes down to larger projects I was looking for a different structure.

For those who know me, know that I can spend hours of hours to find the name of a method or in this case defining a directory structure. It's not done within 5 minutes. It took me weeks or months. Playing around and improving. Running into issues and improving it again. Let me show you what I came up with and what I think is a real alternative to the current default structure.

/app

I wanted the app folder to be really simple and empty. In my experience, in this folder, there should nothing be more than the kernels. One for the Console (CLI) and on for the Http. What I did is moving the two kernels at the very first level to app and renamed them accordingly.

  • app/Http/Kernel.phpapp/HttpKernel.php
  • app/Console/Kernel.phpapp/ConsoleKernel.php

Oh and don't forget to modify bootstrap/app.php .Point the kernels to the new paths.

/src

Prerequisites

Because I want to have all my custom stuff in one place and separate the framework from the domain logic I created a src folder. To autoload all the classes it's necessary to update the composer.json. Let's see how it works.

composer.json
"autoload": {
    "psr-4": {
    "App\\": ["app/", "src/"]
}

Now it's possible to use the \App namespace in the src directory too. The autoloader checks the app directory first and if it doesn't find there the class you would like to autoload, it looks in the src directory. To take that effect don't forget to tell composer that.

$ composer dump-autoload

Move default things over

Before we start diving deeper into it, let's move all the default things over.

  • app/Providerssrc/Provider
  • app/Http/Middlwaresrc/Middlware
  • app/Http/Controllerssrc/Controller
  • app/Exceptionssrc/Exception

Because we moved the defaults, it's again necessary to modify some of the references. These are for the exceptions

  • bootstrap/app.php

for the providers

  • config/app.php

and for the middlewares

  • app/HttpKernel.php

You might have noticed that I did not only move some folders, I also renamed them singular. I just want to have uniformity. With the default structure, I got the feeling that there is missing some consistency. Therefore I decided to go with singular all over the place.

The idea

The last couple of years I basically follow one simple rule in the src directory. If I need to extend something or create an own component I use the namespace of that class where it originally comes from. Let me give you an example.

I often create a base Eloquent class which can be extended. In that base class, I set the guarded property to an empty array because I want to have full control and I know what I'm doing. Anyway... People often ask me where they should put now this kind of class and here is my thumb of rule.

Put it where it originally comes from!

Originally it comes from Illuminate\Database\Eloquent\Model, so the decision is very easy now. The src directory is kind of your individual namespace and can be seen as a placeholder for <vendor>. With that in mind, there is only one place possible and this place is src/Database/Eloquent/Model with the appropriate namespace. The result is something like this:

app/Database/Eloquent/Model.php
<?php

namespace App\Database\Eloquent;

use Illuminate\Database\Eloquent\Model as BaseModel;

class Model extends BaseModel
{
    protected $guarded = [];
}

The Domain

Now it comes to the interesting part. The Domain folder under src. Within the domain folder, I put my domain logic. I separate it by domain objects. Domain objects can be everything related to your domain. In my case, these are objects like User, Tenant, Subscriber, etc.

And within that object folder, I place all the related stuff to that object. For now, these are

  • Models
  • Repositories
  • Exceptions
  • Enums
  • (Form)Requests

In the future, there will be more like

  • Events
  • EventListeners
  • Aggregates
  • and much more

There are some advantages I can see there.

First of all, it's very capsulated. If you don't need one domain object anymore, just delete the folder and you're good to go. You don't have to browse several folders to find related classes. Chances are high that you forget something.

Second is, you have all in one place. There is no more the question of where to put a class. If you only have a repository or a provider it might be easy to put it earlier within a Providers or Repositories folder. But if you need some more custom classes I always asked myself where to put it because these kind of classes are neither Providers or Repositories. Imagine a SubscriberCounter, SubscriberCounterInterface, SubscriberFactory or something like that. Where did you put it before? Now it's clear. Well quite. In any case within the src/Domain/Subscriber folder. Let me give you some more examples where to put something:

  • src/Domain/Subscriber/Job/CreateSubscriberJob.php
  • src/Domain/Subscriber/DTO/SubscriberAddress.php
  • src/Domain/Subscriber/Event/SubscriberCreatedEvent.php
  • src/Domain/Subscriber/Rule/AllowedEmailRule.php
  • src/Domain/Order/Factory/OrderFactory.php
  • src/Domain/Order/EventListener/SendConfirmationMailListener.php
  • src/Domain/Order/Policy/OrderPolicy.php
  • src/Domain/Order/Policy/OrderPositionPolicy.php
  • src/Domain/Account/Notification/AccountDeletedNotification.php
  • src/Domain/Article/ArticleNumber/ArticleNumberGeneratorInterface.php
  • src/Domain/Article/ArticleNumber/IncrementingArticleNumberGenerator.php
  • src/Domain/Report/Mail/WeeklyReportMail.php

I like this idea a lot and if you are used to using it really makes fun.

What about tests?

Well, for tests I only did a couple of changes.

Instead of using Feature and Unit the folder at a first level within tests I decided to create a Cases directory. Why? Easy. The same reason why I created the src directory and a Domain directory within it. With that structure, I can create more directories at the level of the tests where are needed for tests but are not tests itself. Let me give you also some examples.

  • tests/Factory/UserFactory.php
  • tests/Fixtures/UserFixture.php
  • tests/DataProvider/ArrayProvider.php

The Feature folder is still a flat directory. But within the Unit folder, I always create an exact copy of the src directory. As you can see I created a Domain folder and under it, I use the same structure as above. That makes it very easy to navigate and have all the classes in the same place.

Conclusion

For me personally, this makes a lot of sense. This makes my daily work much easier and much more efficient. What is your opinion? Let me know what you think and what your approach is. You can contact me on Twitter at @stefanbauerme.

If you enjoy my articles or you like what I'am talking and twittering about, you should sign up. I will show you everything I know.
I send e-mails very irregularly. Your data is safe with me.