Practical guide

How to design the architecture of a larger PHP application

Do not start with a diagram for the whole world. Choose one important process, name its rules, and build layers around it afterwards.

25 minutes · PHP architecture

First, the short version

Architecture is mostly an agreement

Architecture says where rules, data, and technical details belong. Good architecture does not make an application “enterprise”. It lets people change one thing without fearing ten side effects.

DDD helps you start with the language of the business. The domain is where the company’s rules live: for example, when an order can be sent or an invoice approved.

Get ready

What you need before drawing folders

Understand the problem first. Folders and class names come afterwards.

  • One concrete flow, such as creating an order, approving a quote, or changing a complaint status.
  • Someone who knows the business rules and can confirm what should happen in both normal and failing situations.
  • A PHP project where you can make small incremental changes. You do not need to restructure all of src/ on day one.
  • A place for short notes. One page of terms, rules, and examples is enough at first.

Steps 1 to 3

Put boundaries around real work

A layer is not a folder for the sake of a folder. It needs a clear role and a clear idea of what must not cross it.

1. Name the domain in human language

  1. Write three to five sentences that explain one process without technical words. For example: “an order can be sent only after successful payment”.
  2. List important nouns and verbs. They become names for classes, methods, and use cases.
  3. When two people understand one word differently, distinguish it early. An unclear name in code costs more than one extra class.
Official Symfony recommendations for application structure

2. Split responsibilities into layers

  1. The domain holds rules and business terms. It should not know about HTTP, SQL, or a particular database.
  2. The application layer composes a use case: it loads data, calls domain rules, and saves the result. A CreateOrder class is a good fit here.
  3. Infrastructure handles technology: a database, email, a file, or an external API. It implements interfaces the application needs.
  4. An entry layer, such as a Symfony controller or CLI command, only translates input into a use case and returns a response.
mkdir -p src/Domain src/Application src/Infrastructure
Official Symfony documentation for services and their wiring

3. Protect the dependency direction

  1. Inner layers must not import concrete Symfony, Doctrine, or SDK classes. Otherwise rules quickly become technical details.
  2. When the application needs to save an order, it depends on an OrderRepository interface. The database implementation belongs outside, in infrastructure.
  3. Write every new dependency direction briefly in the README. Architecture then will not be just a diagram from last year.
php bin/console debug:container --types
Official Symfony documentation for typed dependencies

Step 4

Check boundaries on one use case

The best architecture test is a small change. Try making it without touching everything around it.

  1. Find the domain rule

    Open the use case and point to the class or method where its main rule actually lives. If it is hidden in a controller or SQL query, move it closer to the domain.

  2. Replace a technical detail in a test

    The use case should be testable with a simple in-memory repository substitute. It should not need a database connection or web server.

    php bin/phpunit
  3. Check layer dependencies

    Look for framework imports in the domain. An individual occurrence may have a reason, but it should be a deliberate exception rather than the default.

    rg "Symfony\\|Doctrine\\" src/Domain

If something goes wrong

Common problems

You are rebuilding the whole project at once

Choose one use case, surround it with tests, and move it first. A large “architecture sprint” often only stops feature delivery and proves nothing.

Empty layers full of forwarding methods appear

A layer should exist because of a rule or boundary, not because of a name from an article. If it has no responsibility yet, do not create it yet.

The domain calls a database or external API directly

Add a small interface in the application or domain part and put the concrete client in infrastructure. Rules can then be tested without the network.

The team uses different names for the same thing

Keep a short glossary beside the module and reflect it in class names. Consistency is more valuable in a large application than a perfectly clever name.

Done

You know where rules and technology belong.

DDD can now be a compass, not a mandatory template. Add boundaries gradually where the application truly grows.

Request a call

I will call you on the next working day between 9:00 and 17:00.

You can also call me directly.

+420 605 181 728

Leave your phone number and send a callback request.

By sending, you agree to processing your data in order to handle your request.