Home >Backend Development >PHP Tutorial >Another Way to Structure your Symfony Project

Another Way to Structure your Symfony Project

Mary-Kate Olsen
Mary-Kate OlsenOriginal
2024-12-05 03:55:16620browse

The MVC Services architecture is so common in Symfony projects that it feels like the only way. It’s simple, familiar, and works... until it doesn’t. As your project grows, cracks begin to show: your business logic is everywhere, app behavior is unclear, and maintaining the code becomes painful. While it is the most common approach, Symfony does not force you to stick with it.

What if there was a better way ?


The Frustrations of Using the MVC Services Architecture

 

Domain Logic is Spread Everywhere

As a project grows, business logic tends to spread across the entire code base. Every layer of the project — controllers, services, forms, entities — ends up containing bits and pieces of the domain model. This makes it increasingly difficult to focus on any specific part.

 

The Project Boundaries Are Not Clear

When your architecture is organized around technical layers, it becomes harder to identify clear boundaries between different contexts as the project grows. This lack of clarity can lead to tightly coupled code and maintenance challenges.

Another Way to Structure your Symfony Project

 

The Project Behaviors Are Not Clear

Since the default architecture emphasizes technical layers, it becomes quite challenging to understand the behaviors of the project. You might infer that certain entities are managed by specific services or guess the database schema, but the actual behaviors of the project, which are the most important aspects, remain unclear and implicit.

Another Way to Structure your Symfony Project

 

Different Lifecycles

When business logic is scattered across the project and mixed with implementation details, it becomes difficult to evolve them independently. Over time, The lifecycle of business logic tends to be much longer than that of implementation details (such as frameworks, third-party APIs, or databases). This mismatch forces you to rewrite large portions of code whenever even minor changes occur in a dependency.


A Better Way to Structure Your Architecture

 

Isolate the Business Logic

To make it easier to focus on the business logic when needed, the first step is to isolate it from the rest of the project. To achieve this, create a Domain folder. This folder becomes the core of the project, where business logic is modeled using pure PHP objects, free from dependencies on implementation details. While the rest of the project depends on this folder, the Domain folder depends on no one.

Inside the Domain folder, files should be grouped by domain purpose rather than technical purpose. This means no Entities, Services, or Controllers folders here, only folder names that correspond to features or domain concepts.

Another Way to Structure your Symfony Project

 

The Front Door of the Domain

The most important aspect of a project is what it does, the actions it can handle. These actions represent the project's behaviors and should serve as the only way to access the business logic. To reflect this, create an Application folder that explicitly showcases all the project's behaviors. For example, as a new developer on the project, I should be able to understand at a glance what the project is capable of doing by looking at this folder.

Another Way to Structure your Symfony Project

 

Connecting to the Outside World

With the App and Domain folders, it becomes easy to focus on the business logic. However, at some point, this business logic needs to interact with external systems. To handle this, create a third folder called Infrastructure, which contains all the implementation details such as framework-specific code, database connections, and libraries.

Files in the Infrastructure folder depend on the App and Domain files. For example, they might call an application handler from the App folder or implement an interface defined in the Domain folder.

Another Way to Structure your Symfony Project

Concretely, in Symfony, this involves modifying the Controller folder and declaring which services implement which interfaces.

# config/routes.yaml

controllers:
    resource:
        path: ../src/Catalog/Infrastructure/Controller/
        namespace: App\Catalog\Infrastructure\Controller
    type: attribute

 

Reveal the Boundaries

As your project evolves, you might notice that some parts of the business logic deserve their own space in the architecture. A good indicator is when the same term starts to have different meanings depending on the context. For example, the word Product might refer to a factory product, a warehouse product, or an e-commerce product, each requiring its own model. A god object can also be a good indicator; the User class often has this type of issue in Symfony projects.

When this happens, it’s time to extract it and let its business logic evolve independently.

Some contexts will form the core of your project, while others will support it. Generic contexts, such as Auth, can use a simpler architecture because they are not central to your domain

Another Way to Structure your Symfony Project

In this picture, we can see that the Auth context uses a standard Symfony structure, the Order and Catalog contexts use a domain-focused architecture, and the Shipping context uses a feature-focused architecture.

 

Incremental Modularity

If a specific context grows to the point where it needs to scale independently, consider splitting it into a separate deployment unit.

However, don’t rush into this step. Start by making your project modular, and if you notice that a context needs to scale individually, then deploy it separately.
Only split the codebase if organizational challenges arise, such as two teams struggling to collaborate on the same codebase.

Another Way to Structure your Symfony Project

Go Further
  • Simon Brown - Modular Monoliths

The Concepts

As we explored these solutions, we applied several craftsmanship concepts. Let’s name and briefly explain them, so you can dive deeper into each one.

 

Ubiquitous Language

Behind this unusual term lies a very simple concept. The ubiquitous language is the vocabulary your team uses to describe your domain model. This vocabulary should be documented and consistently used everywhere, in product conversations, the codebase, and beyond.

Concretely, create a markdown file at the root of a Bounded Context and bring together product people, domain experts, and tech teams to define each concept of your project.

Go Further

  • Eric Evans - DDD - Chapter 2 : Communication and the use of language
  • Martin Fowler - Ubiquitous Language

 

Bounded Context

A bounded context defines the linguistic boundary within your project, separating parts of the system where the ubiquitous language no longer aligns. Tools like Context Maps and Event Storming can help identify these boundaries.

A bounded context is an abstract concept; it can be implemented in many ways, from a simple folder in a modular monolith to a cluster in a microservices architecture.

Go Further

  • Eric Evans - DDD - Part IV : Strategic Design
  • Vaughn Vernon - IDDD - Chapter 2 : Domains, Subdomains, and Bounded Context
  • Martin Fowler - Bounded Context

 

Ports and Adapters, Hexagonal, Onion, and Clean Architecture

All these architectures aim to isolate business logic from implementation details. Whether you use Ports and Adapters, Hexagonal, or Clean Architecture, the core idea is to make the business logic framework-agnostic and easy to test.

Once you have this in mind, there is a full spectrum of implementations, and the best one depends on your context and preferences. A major advantage of this architecture is that, by isolating your business logic, it enables much more efficient testing.

Go Further

  • Alistair Cockburn - Hexagonal Architecture
  • Uncle Bob - Clean Architecture
  • Herberto Graca - DDD, Hexagonal, Onion, Clean, CQRS, … How I Put It All Together

 

Screaming Architecture

The idea of organizing folders and files to "scream" the business logic is known as Screaming Architecture. This concept emphasizes that the structure of your code should make the project’s purpose immediately clear. The goal is for a new developer to understand what the project does at a glance.

I highly recommend reading Uncle Bob’s article on the topic—his comparison to a house plan is particularly insightful.

Go Further

  • Uncle Bob - Screaming Architecture

 

Vertical Slicing Architecture

Vertical slicing organizes your project by features, allowing each feature to evolve independently. It enables you to apply different architectures to different features based on complexity and maturity.

Although the idea is interesting, it requires highly skilled engineers to implement and maintain such an architecture effectively.

Go Further

  • Jimmy Bogard - Vertical Slice Architecture
  • CodeOpinion - Vertical Slice Architecture, not Layers!

Final Thoughts

The way you structure your Symfony project has a profound impact on its scalability, maintainability, and clarity. By isolating your business logic and making behaviors explicit, you’ll create a system that’s easier to understand and evolve.

If you’re new to these ideas, don’t worry, software craftsmanship is a journey, not a destination. The concepts might seem overwhelming at first, but each one will help you deliver more value to your business.

Have questions or want to share your experience ? Drop them in the comments ! And stay tuned for the next article ?

The above is the detailed content of Another Way to Structure your Symfony Project. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn