PHP Modernization

What a Useful PHP Modernization Audit Should Actually Tell You

An audit is not a security checklist and not a rewrite proposal. It is a written answer to what hurts, what can stay, and what should happen first — including leave it in PHP.

Teams ask for a modernisation audit when they are tired of arguing. One camp wants PHP 8.4 and a framework bump. Another wants microservices. A third wants Go because a conference talk was convincing. None of those are measurements.

A useful Migration Readiness Audit answers a short list of questions with evidence from this system. A poor audit produces a 40-page inventory of every file and a recommendation that happens to match the vendor’s bench. If the document cannot say “do not extract anything to Go,” it was not an audit. It was a proposal.

This is whole-system assessment: architecture, data, deployability, dependencies. It is not the same as measure PHP performance before rewriting, which is the profiler-and-EXPLAIN track. You often need both. They are not substitutes. Performance investigation tells you why a request is slow. An architecture audit tells you why every change is slow, why upgrades are blocked, and whether any workload is actually constrained by the runtime.

If the only pain is latency, start with performance. If the pain is “we cannot ship, we cannot hire, we cannot patch, and we cannot agree what to do next,” start here.

What the audit must answer

What is actually causing pain? Slowness, release fear, hiring, incident load, an upgrade that is now blocked, a partner integration that cannot move. If you cannot name the pain, you will modernise the wrong layer. “Technical debt” is not pain. “A deploy takes Friday night and still rolls back” is pain.

What can stay unchanged? Most of a working product should stay. The audit’s job is to protect that surface from a fashion. A catalogue of every ugly class is not a plan. A short list of what is not on the critical path is.

What can be modernised inside PHP? Framework upgrades, module boundaries, test harnesses, PHP version, queue hygiene. This is usually the bulk of the value. It is also the part vendors skip when they are selling a new stack.

What should be upgraded first? Runtime versus framework versus the one abandoned library that pins both. Order matters. Upgrading the framework on an unsupported PHP is how you get two projects that block each other.

Where are architecture boundaries missing? Not “we need services.” Where does a change in billing surprise shipping, and why is that a code problem rather than a people problem. If the only boundary is a folder name, you do not have a service candidate. You have a naming convention.

Where are the bottlenecks? If they are queries and N+1s, language is not the lever. If they are fan-out and long-lived concurrency, PHP versus Go by workload becomes relevant. The audit should say which, with a pointer to evidence, not with a preference.

Which workload, if any, is worth extracting to Go? One. Named. With a rollback. Workers are the usual candidate; a random CRUD module is not. “The API” is not a workload. “The export consumer that holds 400 MB per job and never keeps up on Monday morning” is a workload.

What metrics create a baseline? p95 on the hot endpoints, error rate, queue lag, deploy frequency, mean time to recover. Without a baseline, “better” is a feeling, and feelings cannot survive a steering committee.

What are the migration risks? Dual writes, data ownership, sessions, cron that everyone forgot, the report that SELECTs from the primary. Risk is not “Go is unfamiliar.” Risk is two writers to orders and no way to reverse a canary.

What should happen first? A sequence of weeks, not a vision of the end state. If the first thirty days are “stand up a platform team,” the audit failed.

Whether the PHP monolith should be split into services is a downstream question. The audit should say if you even have a boundary to split on. Splitting a tangle is how you pay what drives PHP modernization cost twice: once to distribute it, once to understand it.

What a useful audit is not

  • A generic OWASP walkthrough, unless security is the stated pain
  • A count of lines of code as a complexity proof
  • A tool dump from Psalm or PHPStan without a ranked list of what blocks an upgrade
  • A recommendation to hire a large team to rewrite everything
  • A promise of a percentage performance win
  • A dependency graph printed as a poster and called a strategy
  • An inventory of every table with no statement of who should own writes

Static analysis is an input. So are deploy logs, pg_stat_statements if you have it, and a conversation with the person who gets the 3 a.m. call. The output is a decision, not a scanner report.

Security matters. It is not this document’s job to impersonate a penetration test. If the blocker is an unsupported PHP with known CVEs, that belongs in the upgrade ranking. If the blocker is “we have never thought about authz,” that is a different engagement. Mixing them produces a 60-page PDF nobody acts on.

Inputs that make the timebox honest

An audit that cannot see the system will invent it. Minimum useful access:

  • The repository, including the lockfile and the bits people are ashamed of
  • How you deploy, including the parts that are not in the README
  • Staging that resembles production, or an honest statement that it does not
  • Production metrics you already have: APM, slow logs, error tracker, queue depths
  • A walkthrough with someone who has been paged for this application

You do not need to start rewriting modules during the assessment. If a vendor’s “audit” immediately produces implementation tickets in their backlog, you are already in a build engagement. That can be fine if you meant to buy one. It is not an independent look at whether the build should happen.

Sample deliverable structure

Keep it short enough that a CTO will read it. Ten pages with decisions beats forty pages of appendix.

  1. Context. Product, team size, PHP and framework versions, hosting, the pain in one paragraph.
  2. Baseline. The three metrics you will use later to say whether work helped. If you cannot measure them today, the first recommendation is to instrument, not to extract.
  3. Dependency and runtime map. What pins the PHP version; what is abandoned; what is actually unused. Unused code is a gift: deleting it is cheaper than upgrading it.
  4. Architecture sketch. Not 50 boxes. The two or three couplings that make change expensive. Name the modules, the shared tables, the events that do not exist yet.
  5. Data ownership. Which database objects are shared; which jobs write behind the app’s back; which reports will break if you move a write.
  6. Test and release posture. What a change costs today. If the answer is “a weekend,” the architecture conversation is premature.
  7. Ranked work. Stabilise / upgrade / bound / extract, each with a reason and a “do not do this yet.”
  8. Go / not Go. Explicit. If extract, which workload, why, how you roll back. If not, say so in a sentence a sceptic can quote.
  9. Risks and unknowns. The things the audit could not see in the timebox (no staging, no slow-query log, no one who remembers the import job). Unknowns are not padding. They are the difference between a plan and a wish.
  10. First thirty days. If they hire nobody, what should the internal team still do. An audit that only produces paid follow-on work is a sales document.

That structure is also how you later explain cost without inventing a total. The expensive items in the ranked list are the budget drivers.

How long and how invasive

A serious look at a production PHP system is days of senior time, not an afternoon. A half-day workshop can surface opinions. It cannot tell you whether the export job and the HTTP app share a write to the same table.

The timebox should be fixed. Open-ended “discovery” is how assessments become unpaid architecture while someone quietly starts a rewrite. Fixed price, written output, stop. If more digging is needed, that is a named follow-on, not an expanding blob.

Invasive does not mean disruptive. Read-only access, a staging clone, a profiler already in production, a conversation. You should not need a change freeze to understand the system. If you do, that fact belongs in the deliverable: the organisation cannot currently observe itself.

How to use the document internally

The audit has to survive contact with three audiences.

Engineering needs a sequence they can execute without waiting for a vision. Product needs to know whether the next quarter’s roadmap is blocked on this work or can proceed in parallel. Finance needs to know why a fixed assessment is not a rewrite quote, and what would have to be true before anyone should fund phase two.

If those three cannot all read the same document, rewrite the document. Do not produce a technical appendix and a separate “executive” fiction. The recommendation is the same in both rooms, or it is not a recommendation.

A valid outcome is to keep the system in PHP

This has to be sayable in the first meeting. Otherwise you will only hear extraction. Plenty of systems need four indexes, a PHP 8.3 bump, and a boundary around billing — not a new language.

Another valid outcome is “upgrade, then stop.” Another is “extract this worker, leave the HTTP app.” Another is “do not start a programme this year; fix deploys and tests first.” All of those are cheaper than a rewrite that begins because the team was tired of the argument.

If you want that answer written against your architecture, the commercial form is the Migration Readiness Audit at €1,200: scoped discovery, the structure above, no obligation to implement. If implementation follows, it is priced from €550/day and only where the document said it should. Start from the Migration Readiness Audit page when the argument in the team is costing more than a fixed assessment.