Extended Symfony project structure.

Alexander Bondars
5 min readAug 4, 2024

--

Application, Domain, Infrastructure.

If you were ever interested in top-level programming architectures, you might find that almost every existing programming concept stands for splitting the responsibility between components, forming them into a less dependent state, and in some way chasing the Single Responsibility ideas.

In my experience, the most popular approach is to follow the Layered Architecture principles and DDD methodology.

Diving deeper into DDD details and architecture patterns might take a lot of time and effort before you can start doing anything. However, simply following some folder structures can help you with a basic understanding of the fundamentals and give you material for future learning.

While frameworks usually provide you with a prebuilt folder setup to make your life easier so you can start developing immediately, a real project architecture requires a more flexible and sufficient structure, with clear responsibility segregation for better control.

Default Symfony application structure.

What’s wrong with that? I suppose nothing, especially if your project is a small microservice without a decent amount of business rules. But for quite a large application, it’s better not to put all eggs in one basket. That’s why I want to show you a possible way to organize everything in layers:

  • Application
  • Domain
  • Infrastructure

Application.

What should be put there? Anything that glues the framework with your business logic (Domain layer) and makes the whole application react to user’s actions. Controllers? Sure! Controllers use the framework’s request to pass the data to the domain services and return responses. Dependency Injection configuration? Yes! Because it takes the framework’s container and adjusts it with domain services’ behavior features. Security setup? Of course! It’s the place for all your custom authenticators and authorization logic as well. The same should be done for Forms, Messenger middlewares, Doctrine custom types, etc.

For Symfony it means this:

Folders structure for the Application namespace.

More detailed view contains classes as well (I skipped interfaces and some structures for the sake of simplicity):

Structure example in details.

There you can find not only everything I mentioned before, but also Listeners for Kernel Events, such as modifying response data in case of an Exception or changing route parameters on the emitted Request Event. Along with Controllers, you can find Commands, which set up ways to interact with your project in the CLI environment. You will also find Compiler Pass configurations, specifying your services’ loading nuances for the Container, or custom validation Constraints for Requests and Forms. And so on and so forth…

So, in summary, the responsibility of the Application layer can be described by these statements related to the project itself:

  • How to exists;
  • How to handle input;
  • How to handle output;
  • How to communicate with your Business logic and Infrastructure;

Domain.

On the one level deeper exists Domain layer. This is the heart of your business logic, and this is exact the place where you define all your Repositories, Models, Entities, layers of Services, and Components. I can’t predict the structure you might have there because every project’s domain is unique in structure, but I can surely say one thing — leave this place free from the Framework’s references as much as possible. It doesn’t mean you should suffer from the pain of wrapping your brain around finding a replacement for Doctrine Collections in your Entities, but having, for example, listeners for kernel events here smells bad, and you should put them into the Application, because it clearly looks like you want to adjust the Framework’s behaviour.

Domain might have this structure:

Example of the Domain namespace internals.

In the ideal world and according to the best practice recommendations, communication between the Domain and Application should be established only through Domain Services or using Commands and Handlers (in case of CQRS). Therefore, make sure your Entities and Models are not exposed to the layer above, because the encapsulation principle works not only at the class level but also can be applied to groups of classes or even groups of components.

Infrastructure.

At this point, we have already integrated our business logic with the framework. Your application is now almost complete and can receive input, make decisions, and return output. And this is kind of everything you need for the project to be working.

On the other side, opposite from the user interaction, we have interaction with third-party systems providing us with APIs and data exchange. I’m speaking about different filesystem tools, caching adapters, adapters for API integrations, databases, and many more implementations of facades for external (in relation to our application) services.

Why don’t we put it inside the Application or Domain then? The Application works with the Framework and IO operations, while the Domain contains structures for your business rules. Therefore, the place for different kinds of external integrations is in the Infrastructure 🤷‍♂️.

Infrastructure namespace example.

You can find Facades for Facebook and Google APIs, Adapters for payment systems SDKs, and caching tools there.

Even though you have vendor packages providing you with full functionality, sometimes it might be useful to have an adapter between your business logic and the SDK for better maintenance.

The SDK might become outdated or be archived (hello Facebook Graph SDK for PHP). External APIs can change versions, drastically updating the original package, or even create a new one as a replacement.

Having your personal Adapter and using it in the Domain will make it easier for you to update vendor packages or replace them because you don’t need to modify a large number of files in your project to apply changes. Just modify the Adapter, and that’s it.

Resume.

With a ton of different ways to build your application, it can be hard to figure out the final concept to implement after reading all the texts and seeing all the diagrams.

But a clear folder structure can illustrate the advantages of layered architecture, making the principles easier to grasp and apply.

Thank you for reading and have fun with coding!

--

--