Duplication in PHP is a semantic problem, not a textual one, because the copies almost always differ in variable names, so grep for $userId will never find the controller that called it $accountId. Atlas attacks it with codebase_search, which finds the copies by meaning, then reads each hit to confirm they are genuinely equivalent, creates the shared helper class with the write tool, and replaces each duplicate with a call using apply_patch, one file per patch so every swap is independently reviewable and revertible. Atlas runs PHPUnit with bash after each swap, and finishes by grepping for any surviving copy. Composer wires the new namespace into the autoloader, and PHP-CS-Fixer keeps the result PSR-12 clean.
How do you find duplicated PHP code that grep cannot see?
Ask codebase_search for the behavior, not the exact code. Duplicated PHP is a semantic problem: the same 15 lines of date normalization appear in 4 controllers with $startDate, $from, $begin, and $d as variable names, and no single grep pattern matches all four.
Copy-paste in PHP mutates on contact. A developer pastes a block into a new controller, renames the variables to match local conventions, swaps a Carbon call for a DateTimeImmutable one, and reorders two guard clauses. The result is semantically identical and textually distinct, which is precisely the case grep is blind to. Atlas searches code with hybrid semantic and keyword retrieval fused by reciprocal rank fusion, so a query describing the behavior surfaces all four copies. The six Atlas tools this workflow uses are codebase_search, read, write, apply_patch, bash, and grep, and codebase_search is the one that makes the rest possible, because you cannot collapse duplicates you never found.
How do you confirm two PHP code blocks are really duplicates?
Read each codebase_search hit and confirm the copies are genuinely equivalent before collapsing them. Two PHP blocks that look identical can differ in one guard clause, and the controller that added an extra is_null check in 2024 did so because a real bug required it.
The dangerous refactor is the one that unifies four copies while quietly deleting the one behavior that made the fifth different. Atlas reads every hit rather than trusting the similarity score. In PHP the divergences hide in specific places: loose comparison with == where a sibling used ===, a null coalescing operator that one copy has and another does not, a type juggling assumption about a string that arrived from $_POST as a string but from the database as an int. Atlas indexes code by AST declarations using tree-sitter, not blind line windows, so what Atlas reads back is the whole method rather than a window that could cut off the guard clause that matters.
Why replace each PHP duplicate with apply_patch one file at a time?
Atlas replaces each duplicate with a call using apply_patch, one file per patch, so each swap is independently reviewable and revertible. Collapsing 5 PHP controllers in one giant commit means that when one of them regresses, you revert all 5 or hand-untangle the diff.
Granularity is a safety property here, not a style preference. Each PHP controller that loses its inline copy and gains a call to the new helper is a self-contained change: a use statement at the top, a method call in place of 15 lines, and nothing else. Reviewed alone, that is a one-minute read. Bundled with four siblings, it is a diff nobody reads carefully. apply_patch enforces the discipline by working file by file, and it seeks its hunk context rather than blindly replacing, so a patch that no longer matches the file fails instead of misapplying. Atlas snapshots file changes as git patches, so any single swap can be rolled back on its own.
How do you prove the PHP refactor did not change behavior?
Run PHPUnit with the bash tool after every swap, not once at the end. Collapsing 5 duplicated PHP blocks into one helper is 5 opportunities to change behavior, and finding out which one broke is far cheaper when the suite runs between each apply_patch.
Running PHPUnit once at the end tells you that something broke. Running it after each swap tells you what. Because each apply_patch touches exactly one PHP file, a red suite immediately after one of them names the culprit without bisection. Add a PHPUnit test for the new helper class itself, covering the union of edge cases the copies handled, so the extracted method is better tested than any individual copy was. Then run PHP-CS-Fixer so the new class and the edited controllers conform to PSR-12 rather than showing up in review as a whitespace diff. Composer manages the dev dependency for both PHPUnit and PHP-CS-Fixer.
How do you find PHP copies that survived the refactor?
Finish by grepping for any surviving copy. A refactor that collapsed 4 of 5 duplicated PHP blocks and left one behind is worse than no refactor, because the codebase now has a shared helper and a rogue copy that will drift from it within one sprint.
The last grep is the honesty check. Search for the distinctive fragments of the old implementation, a specific format string, an unusual constant, a characteristic regex, across src/ and tests/. Then re-run codebase_search with the same behavioral query that started the whole workflow and confirm the only hit is the new helper class. Atlas can build its code index with local Ollama embeddings, keeping code off third-party servers, which matters for a proprietary PHP application. When both searches come back clean, run the full PHPUnit suite once more, and let Atlas stage and create the commit, since Atlas reads git branches, status, and diffs and can commit on your behalf.
Step by step
- 01Run atlas in a project with a composer.json so Atlas can read your namespaces, autoload config, and dependencies.
- 02Ask codebase_search for the behavior, not the exact code, to surface near-duplicate PHP implementations that differ only in variable names and that grep would miss.
- 03Read each hit with the read tool and confirm the copies are genuinely equivalent, watching for an extra guard clause or a == where a sibling used ===.
- 04Create the shared helper with write, which shows the full diff in the permission prompt before the file is created, and add its PSR-4 namespace to composer.json if it is new.
- 05Replace each duplicate with a call using apply_patch, one file per patch, so each swap is independently reviewable and revertible.
- 06Run PHPUnit with bash after every swap, so a red suite names the exact file that broke without bisection.
- 07Add a PHPUnit test for the new helper covering the union of edge cases every copy handled, so the extracted method is better tested than any copy was.
- 08Finish by grepping for any surviving copy and re-running codebase_search, then apply PHP-CS-Fixer so the result is PSR-12 clean before committing.
Frequently asked questions
- how to find duplicated code in a php codebase
- Use Atlas's codebase_search and describe the behavior rather than the exact code. PHP duplicates almost always differ in variable names, so a grep for $userId misses the controller that called it $accountId. Semantic retrieval surfaces all the copies, and then you read each one to confirm equivalence.
- can ai extract a shared helper from copy-pasted php controllers
- Yes. Atlas finds the copies with codebase_search, confirms they are equivalent by reading them, creates the helper with the write tool, and replaces each duplicate with a call using apply_patch, one file per patch. PHPUnit runs after every swap to prove behavior did not change.
- why does grep miss duplicated php code
- Because copy-paste mutates. The pasted block gets renamed variables, a reordered guard clause, and a different date library call, so it is semantically identical and textually distinct. Atlas searches code with hybrid semantic and keyword retrieval fused by reciprocal rank fusion, which is what finds those copies.
- how do I refactor php duplicates without breaking behavior
- Swap one file at a time with apply_patch and run PHPUnit after each swap. Because each patch touches exactly one PHP file, a red suite immediately names the culprit. Read every copy first, since one of them usually has an extra null check that exists because a real bug required it.
- does atlas update composer autoload for a new php namespace
- Atlas can add the PSR-4 namespace mapping to composer.json as part of the change, showing you the unified diff before writing. Run composer dump-autoload afterward so the new helper class is resolvable, and add PHPUnit tests for it before swapping the callsites.
- how do I make sure no copies of the old code are left
- Grep for the distinctive fragments of the old implementation across src/ and tests/, then re-run the same codebase_search behavioral query that started the refactor and confirm the only hit is the new helper. A refactor that leaves one rogue copy behind is worse than none.
- can atlas index a private php codebase without sending it to a server
- Yes. Atlas can build its code index with local Ollama embeddings, keeping code off third-party servers, so codebase_search works over a proprietary PHP application locally.
- what does atlas need to work on a php project
- Run atlas in a project with a composer.json. Atlas reads your namespaces, autoload config, and dependencies, and can add PHPUnit tests or apply PSR-12 formatting through PHP-CS-Fixer, showing you the diff for review before writing.
Try Atlas in your terminal
The terminal-native AI coding agent. Free core, single binary.
Install AtlasRelated guides
Extract a Shared Helper from Duplicated Code with Atlas (2026 Workflow)
How to extract a shared helper from duplicated code with Atlas in 2026: codebase_search finds the copies by meaning, write creates the module, apply_patch swaps each call.
Atlas for PHP in 2026
Atlas, the terminal-native AI coding agent, empowers PHP developers in 2026 with intelligent code understanding, secure workflows, and direct integration for Composer and PSR standards.
Rename a Symbol Across the Repo in PHP With Atlas (2026)
Rename a PHP class, method, or constant everywhere it is used. Atlas pairs the lsp tool's findReferences with grep and an edit tool that refuses ambiguous replacements.
Migrate a Deprecated API Across Every Callsite in PHP with Atlas in 2026
Move your PHP codebase off deprecated functions or modules onto replacements without missing a single caller. Atlas integrates with Composer and PHPUnit for a verified migration.
Onboard to an Unfamiliar PHP Codebase with Atlas (2026)
Build a mental model of an unfamiliar PHP repo in 2026 without reading every file. Atlas maps the Composer layout, delegates sweeps to a read-only explore subagent.
Refactor a Legacy PHP Module with Atlas Without Breaking Callers (2026)
How Atlas refactors a legacy PHP module in 2026: map callers with the lsp tool, pin behavior with PHPUnit, restructure with apply_patch, and format with PHP-CS-Fixer.
Document a PHP Module with a README in 2026
Atlas helps PHP developers in 2026 generate accurate README documentation directly from source code. It leverages Composer, PHPUnit, and PSR standards to describe what your PHP code actually does today.
Plan a multi-file change before editing in PHP with Atlas (2026)
Design a multi-file PHP change before any edit lands. Atlas's plan agent denies edit outside .atlas/plans/*.md, and plan_exit gates the handoff to the build agent.