Pin Legacy PHP Behaviour Before You Upgrade It
An upgrade without a pinned picture of current behaviour is a rewrite you did not budget. Characterization tests record what the system does now, including the bugs you still depend on.
Blog
Notes from real backend work: PHP modernization, Go migration decisions, private AI workflows on infrastructure you control, and the architecture trade-offs behind them. Written for engineering leads who have to justify the call.
Getting an older PHP codebase back under control: framework upgrades, boundaries, tests and delivery speed.
An upgrade without a pinned picture of current behaviour is a rewrite you did not budget. Characterization tests record what the system does now, including the bugs you still depend on.
Laravel majors stall on packages, PHP, Horizon and untested checkout. Shift rewrites files. It does not own your Composer graph or your money path.
There is no honest fixed price for modernising a PHP system you have not looked at. There are cost drivers you can name before anyone invents a rewrite budget.
Release fear is a product constraint. If deploys need a checklist, a hero and a prayer, the pipeline is the work — not a new framework.
A blocked Symfony upgrade is usually a package-ownership problem, not a lack of knowledge about the next minor. Abandoned bundles pin PHP, and tests are what make replacement safe.
Unsupported PHP in production is rarely a knowledge problem. It is a coupling problem: abandoned packages, no tests on the money path, and a release process nobody trusts.
The choice is not keep PHP versus rewrite in Go. It is leave it, upgrade it, refactor it, extract one workload, or — rarely — rewrite. Most teams skip the first four.
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.
A rewrite is a very expensive profiler. This is the order I investigate a slow PHP application in, and the point at which changing language actually becomes the right lever.
What is worth extracting into Go, what should stay in PHP, and how to decide.
PHP-FPM spends a process per request. FrankenPHP and RoadRunner keep the process alive. That can remove the ceiling that teams cite when they ask for a Go rewrite, and it leaves the database exactly where it was.
A Go service beside PHP should not parse PHP's session format. Keep PHP as the session authority, or move both sides onto a session record you defined.
PHP-FPM serves one request per worker process. Memory and connections usually cap concurrency before the CPU does. Go changes that shape; it does not fix a slow query.
Checkout looks like the valuable first Go service. It is the most coupled write path you have. Extract workers, uploads or reports until you can take money without a dual-write fantasy.
A workload-by-workload comparison of PHP and Go, runtime model, memory, concurrency, productivity and operations, without pretending either language wins everywhere.
How to add Go services to a long-lived Symfony platform one workload at a time, Doctrine boundaries, Messenger transports, tracing and rollback, while product work continues.
The first Go service next to PHP is an operations problem: table ownership, message contracts, deploy, and a rollback that does not need a Saturday.
A background consumer has no latency contract, an existing retry safety net and a one-page interface. That makes it the lowest-risk place to find out whether Go actually helps you.
Laravel is not the problem. This is a selective extraction strategy, which workloads leave, which stay, and how to run Go alongside Laravel without breaking auth, queues or the schema.
A workload-first approach to moving parts of a PHP system to Go, including what Go fixes, what it does not, and how to roll out changes without betting the product on a rewrite.
APIs, data ownership, queues, caching and the bottlenecks that actually show up in production.
If PHP commits business state and then publishes to a queue, those two writes will diverge. An outbox table in the same transaction is the fix, and it stays valid when a consumer moves to Go.
Admin exports and GROUP BY on the primary are not a PHP language problem. They are OLTP sharing a disk with analytics. Fix the path: replica, warehouse, or an isolated worker — and only then ask about Go.
Splitting a monolith trades in-process complexity for network complexity. Here is how to decide whether that trade is worth making, and how to make the first cut so it survives production.
How to route traffic, draw boundaries and move data ownership so a PHP monolith is replaced piece by piece instead of rewritten in one jump.
How senior backend capacity is bought, scoped and integrated on a B2B basis in Europe.
An MSA does not say what the engineer will do in your repository. The statement of work does. If it only says senior backend help, you will argue about the invoice in month three.
There is no honest single European day rate. There are bands by country, seniority and specialism, and then there is the fully loaded cost of the engagement you actually need.
The situations where two or three senior days a week solve the problem, the situations where they clearly do not, and how to run the arrangement so it produces real output.
An agency is a delivery organisation. A senior contractor is one engineer inside your team. Those solve different problems, and buying the wrong one is expensive in opposite directions.
You do not hire a contractor the way you hire an employee. Paper, access and a paid pilot can run at the same time. The delays are almost always sequential process, not scarcity of engineers.
The decision is not day rate versus salary. It is time-to-capacity, fully loaded cost, how long you need the work, and who still owns the system when the engagement ends.
Embedded means the same tools and the same definition of done as your team. It is not a weekly call, an agency bench, or a permanent seat you are afraid to open.
A freeze blocks permanent seats. It does not always block the work. The useful move is to name whether you have a capacity problem, a skill problem or an ownership problem, then buy the matching shape of help.
A senior PHP contractor in Europe is useful when you can name the Symfony or Laravel work. Body shops sell a bench. You need someone in the repository.
Interviews show conversation. A paid pilot shows whether someone can work in your repository. Ten days is enough to see that, and too short to prove they should own the domain forever.
A practical guide to engagement models, interviews, total cost and B2B paperwork when you bring an experienced Go engineer into a European product team.
When a local or private model is justified, how the systems are built, and where a public API is still the better call.
On-premise changes where the model runs. It does not decide purpose, retention, deletion, or whether anyone should have pasted that document into a prompt. Those are still a programme, and legal will ask for the engineering half.
How model size, quantization, VRAM, RAM and concurrency interact, with workload profiles you can update as hardware and models change.
One company server is a valid local LLM contour for a narrow workflow and a small audience. VRAM, backups and honesty about what will not fit matter more than the UI.
Hybrid AI is two routes after you classify the data: a private path you operate, and a cloud path you have contracted. It is also easy to overbuild.
What an on-premise LLM looks like as a service: inference, identity, storage, concurrency and the operational work that vendors normally hide.
A side-by-side of local models and cloud APIs: what you pay, what you give up, and a decision tree that does not assume local is the answer.
Tickets are full of customer PII. Classify, summarise and draft on infrastructure you control, keep a human in the loop, and never auto-send a model reply.
How to let people ask questions about internal documents with citations, using local embeddings and a local model, and where that design fails.
Pasting internal text into a public chatbot is a transfer to a third party. Treat that path in policy, contracts and logs — including the option to use no model at all.
How to turn meetings, interviews and recordings into private transcripts, summaries and a searchable archive without sending the audio to a public API.
Running a local model is easy. Running one that reliably improves a real workflow is not. This is the decision framework before anyone buys a GPU.
The engagements below are where these articles come from. They run on one B2B agreement, invoiced monthly in EUR.
Offer 01
Deciding what stays in PHP, what gets modernized and what is genuinely worth extracting into Go. Migration Readiness Audit €1200 fixed.
Review the migration decision
Offer 02
Transcription, document search and internal assistants on infrastructure you control. Private AI Readiness Audit from €1500.
Review private AI deployment
Offer 03
A senior backend engineer inside your team, PHP, Go, APIs and distributed systems, at €300/day, up to 5 days per week.
See how the engagement works