PHP Refactoring & Legacy Modernization

Most PHP applications don’t need a full rewrite – they need a clear modernization strategy. As an expert PHP and Symfony developer, I help product teams progressively refactor existing codebases: extracting business logic from controllers, introducing use cases, splitting into bounded contexts, and building solid foundations for future development. No big-bang rewrites. Steady, measurable improvement aligned with your roadmap.

Legacy PHP refactoring: before (coupled monolith) and after (bounded contexts)

My refactoring approach

Legacy PHP projects share common patterns: tight coupling, no tests, mixed responsibilities, business logic buried in controllers. The challenge is not identifying the problems – it’s making progress without breaking what works.

My typical sequence:

  1. Functional smoke tests – protect critical paths before touching anything
  2. Extract business logic – move it out of controllers into explicit use cases (application layer)
  3. Identify bounded contexts – apply DDD principles to define coherent module boundaries
  4. Introduce abstractions – ports and adapters to prevent implementation details leaking into the domain
  5. Discover and refine – new contexts emerge as the model clarifies; previous contexts get leaner
  6. Enrich the domain model – replace anemic models with proper domain events, integration events, inter-context clients

I also use AI-assisted tools (including Claude Code) to accelerate the codebase mapping phase and speed up pattern detection across large codebases.

This sequence describes Strategy A – progressive extraction. For codebases where the risk is too high to work directly inside the existing code, Strategy B takes a fundamentally different approach (see below).

Why splitting a complex problem into smaller ones changes everything

The core principle behind bounded contexts is simple: a problem that is too large to reason about clearly becomes manageable when split into smaller, well-defined problems. Each bounded context has a clear responsibility, its own vocabulary and its own team ownership. A developer joining the project can understand a single context without needing to hold the entire system in their head.

This is not just a software architecture argument – it has a direct impact on how you work with modern AI tools.

The AI context window effect

A legacy monolith of 500,000 lines of code is impossible to feed entirely to an AI assistant. Even with large context windows, the model loses coherence, misses domain subtleties and produces generic suggestions that don’t reflect the actual business rules.

Once the codebase is split into bounded contexts of 15,000–30,000 lines each, the situation changes completely. Each context fits comfortably within an AI’s working context. Tools like Claude Code can then:

In practice, what used to require weeks of analysis can be done in days. The bounded context decomposition is not just good architecture – it is what makes AI-assisted development truly effective on complex legacy codebases.

Component-based packaging makes it concrete

Splitting into bounded contexts only pays off fully when the codebase structure reflects it. The classic approach, Symfony's default convention, organises code by technical layer:

Layer-based structure (Symfony default, but not ideal) src/ 📁 Controller/ ← all domains mixed 📁 Repository/ ← all domains mixed 📁 Service/ ← all domains mixed

Pointing an AI tool at src/Repository/ gives it a mix of persistence logic from every domain. The context is incoherent – the model cannot reason about a specific business domain because the files don’t reflect domain boundaries.

Component-based packaging changes this:

Component-based structure src/ 📂 Identity/ 📁 Infrastructure/Persistence/DoctrineUserRepository.php 📁 Infrastructure/Http/RegisterUserController.php 📁 Application/UseCase/RegisterUser.php 📄 Domain/User.php 📂 Orders/ 📁 Infrastructure/ 📁 Application/ 📁 Domain/

Now each bounded context is a self-contained package. Pointing Claude Code at src/Identity/ gives it a coherent, domain-scoped context – the full stack of one business capability, nothing else. This is what makes AI assistance precise rather than generic. And as a side effect, it also makes future microservice extraction straightforward: the boundary is already drawn in the file system.

Which strategy fits your project?

Not every legacy codebase calls for the same approach. The right strategy depends on the risk level of the existing code, the team size, and how well the business domain is understood upfront.

Two refactoring strategies: progressive extraction vs bubble context

Strategy A – Progressive extraction
The monolith keeps running throughout. Module by module, clean bounded contexts are extracted alongside ongoing feature delivery – continuous delivery from sprint 1. Business logic moves out of controllers, use cases emerge, the domain model gets richer. The legacy code shrinks progressively. The tradeoff: you’re working inside messy code while improving it, which requires discipline.

Best for: continuous delivery constraint, small team, need for visible results immediately.

Strategy B – Bubble context
The legacy code is isolated in a “bubble” protected by an anti-corruption layer (ACL). New development and refactored modules are built cleanly outside the bubble, communicating with legacy only through the ACL. The bubble gradually empties as responsibilities are transferred out. The tradeoff: there is an upfront investment phase to set up the ACL correctly – during this period delivery slows down. Once the ACL is in place, a steady continuous delivery rhythm resumes.

Best for: highly risky or untouchable legacy, larger team, business domain well understood upfront, long-term internal ownership.

There is no universal right answer. The first conversation I have with a team before touching any code is about which strategy fits their specific situation – and being honest about what each one costs.

Common refactoring missions

Key patterns

Applied selectively based on what the codebase needs, not as a dogmatic checklist.

Clients who trusted me

Sanofi
IAD Immobilier
Leboncoin
Air France

💬 Let’s assess your legacy codebase

← Back to home