Using AI to speed up an application migration without losing control of quality
Where language models save time in a PHP or Symfony migration, where they go wrong, and which guardrails (tests, static analysis, human review, confidentiality) make their use acceptable on production code.
By the Synapsea team · · 4 min read
Coding assistants and AI agents can now read a module, propose its rewrite and generate tests. On an application migration, where much of the work is repetitive, that is real help. It is also a new source of errors if you let them decide. Here is how we use them: as an accelerator under control, never as the pilot.
1. What a language model does well
- Read and summarize unfamiliar code. Explain what an 800-line controller does, spot buried business rules, propose a dependency map. The gain on the inventory phase is clear.
- Propose local refactorings. Extract a method, replace a deprecated call, convert annotations to attributes, adapt a signature. On an isolated class, the proposals are often right.
- Write characterization tests. From an observed behavior (a request and its response, a function and its outputs), write the test that pins it down. Tedious for a human, fast for a model.
- Produce documentation. Describe a migrated batch, explain a choice, write a deployment guide.
2. What it does badly, or dangerously
- Make things up. A method that does not exist in this framework version, a plausible but wrong configuration option, an imaginary Composer package. The error reads fluently, so it is hard to catch by eye.
- Change behavior while "improving". A model that "cleans up" an odd condition sometimes removes a deliberate business rule. Characterization tests exist precisely to catch that.
- Reason about the whole system. Beyond a few files, consistency degrades: inconsistent names, duplication, broken interface contracts.
- Assess risk. A model does not know that a monthly export is loaded as-is by a partner; it will happily reformat it.
3. The guardrails
An AI proposal enters the code only if it clears the same bars as a human proposal — plus a few more.
| Guardrail | Role | | --- | --- | | Characterization tests first | Pin down current behavior; any proposal that breaks them is rejected, even if it "looks better" | | Deterministic tools first | Rector for mechanical rewrites, PHPStan for type errors: what a rule can do is not handed to a model | | Systematic human review | Every proposed diff is read by an engineer who knows the context; no automatic merge | | Small batches | One proposal = a scope readable in one sitting; no "migrate the whole module" | | Static analysis afterwards | PHPStan rerun after each batch, at a level at least equal to before | | Decision log | What the AI proposed, what was accepted, modified or rejected, and why |
The idea fits in one sentence: the AI proposes, the tests and the engineer decide.
4. Confidentiality of code and data
A business application's code and, even more, its production data are sensitive assets. Rules we apply and write into the scoping:
- No production data is sent to an AI service. Tests and examples are built on synthetic or anonymized data.
- The choice of provider and model hosting is made with the client: an external service with contractual no-training and zero-retention commitments, or a model hosted in the client's infrastructure when the context requires it.
- Written agreement before any source code is transmitted, with a precise scope (which repositories, which files excluded: secrets, infrastructure configuration).
- Traceability: what was sent, when, to which service, for which batch.
A client who refuses any external service is not a blocker: deterministic tooling (Rector, PHPStan, PHPUnit) already delivers most of the gain, and local models cover the rest.
5. A typical run
- Assisted inventory: the model summarizes each module; the engineer validates and corrects the map.
- Characterization tests: generated from observed behaviors, reviewed, executed; they become the safety net.
- Mechanical rewrite by Rector; PHPStan analysis.
- Assisted rewrite of the non-mechanical parts, file by file, every diff reviewed.
- Tests + static analysis; the batch ships to production; log updated.
- Next batch.
Takeaways
- AI speeds up inventory, local refactorings, tests and documentation.
- It makes things up, changes behavior and does not weigh risk: without a test safety net and review, it costs more than it saves.
- Deterministic tools go first; the model handles what is left.
- Confidentiality is settled by contract and architecture, before the first request.
This is the approach described in our PHP & Symfony modernization offer: automation under supervision, never a promised automatic migration. For the framework step, see Symfony 6.4 to 7.4 migration.
Sources: Rector documentation (getrector.com), PHPStan (phpstan.org), PHPUnit (phpunit.de); Symfony blog post "Preparing for Symfony 7.4 and Symfony 8.0" on deprecation detection (symfony.com/blog).