The November upgrade train

PHP 8.5 came out yesterday. Symfony 7.4 LTS and 8.0 land at the end of the month, same as every year. November is upgrade month now. First the cargo, then the timetable. From 8.5 I care about three things. The pipe operator makes transformation chains read left to right: $slug = $title |> trim(...) |> strtolower(...) |> (fn ($s) => preg_replace('/\s+/', '-', $s)); The new URI extension puts standards-correct URL parsing in core. parse_url had opinions instead of a specification, I will not miss it. And #[\NoDiscard] lets a method declare that ignoring its return value is a bug. Made for result-style APIs, where a dropped return is a swallowed error. ...

November 21, 2025 · 2 min · Murat Useinov

Coding agents meet legacy PHP

A strange-looking condition in a 2015 module, “fixed” by an agent. All tests green, static analysis clean, and the condition was a business rule with history. My review caught it. Barely. That came out of an experiment I ran this month: a coding agent on one legacy PHP module, since agents can do multi-file changes now and every old codebase suddenly looks like a target. The setup matters more than the model. I did not ask “modernize this”. I gave a narrow migration goal on one bounded module: replace a deprecated API, add proper namespaces, keep behavior. The same shape of task I would give to Rector. The difference is that the agent also handles the irregular cases Rector cannot express, the places where people wrote creative code in 2015. ...

October 10, 2025 · 2 min · Murat Useinov

PostgreSQL 18, the upgrade notes

PostgreSQL 18 went GA yesterday. I ran the beta in July. Now it is not a preview, it is a ticket, and these are the notes from planning it. The quiet feature that matters most for the upgrade itself: pg_upgrade now carries planner statistics over. Before, the minutes or hours after switching versions were the scary part. Fresh cluster, empty statistics, the planner guessing, production limping until ANALYZE finished. That cliff is mostly gone. This alone changes how a major upgrade feels at 2 a.m. ...

September 26, 2025 · 2 min · Murat Useinov

vendor/autoload.php is not free

46 milliseconds. That is how long the bootstrap of one large application took before the framework even started, in a trace I pulled this week. A good share of it was autoloading. require vendor/autoload.php looks like a constant of nature. With plain PSR-4 rules it is a loop: every class load walks the prefixes and asks the filesystem whether a file exists. A few thousand classes on a cold request, and the loop becomes a number you can see in a flame graph. ...

August 20, 2025 · 2 min · Murat Useinov

A weekend with PostgreSQL 18 beta

A staging copy of one database, restored over the weekend onto PostgreSQL 18 beta. GA is expected in autumn. Two things I wanted to touch: asynchronous I/O and the built-in uuidv7(). Start with uuidv7, because it is about a mistake many of us already made, me included. Random UUIDv4 primary keys scatter inserts across the whole index. Every insert lands on a random page, the working set is the entire index, buffers churn. UUIDv7 is time-ordered: new rows go to the same few pages, like a sequence, but still globally unique. ...

July 2, 2025 · 2 min · Murat Useinov

One TTL does not fit the whole page

“Hello, Ivan” in the header. That is what broke the full-page cache on one project: either the whole page became uncacheable, or Ivan’s greeting was served to strangers. We managed both, in the same month. Full-page cache behind a reverse proxy is a great first step. It dies on the first personalized element. The way out is to stop thinking of the page as one object. It is regions with different speeds. The header with the user name changes per user. Navigation changes per deploy. The product list changes every few minutes. Prices and stock change every few seconds. Give all of that one TTL and you must pick the minimum, which means almost no cache at all. ...

June 16, 2025 · 2 min · Murat Useinov

Symfony 7.3 and the code I get to delete

Symfony 7.3 was released this week. I opened the release notes with a text file next to them. The file is a list of things to delete. Every project older than three years carries a layer of custom helpers that were reasonable when written. The framework moves, the helpers stay. Nobody reviews them, because they work. Two examples from one project. A base console command class with sugar for arguments and options, written around 2021. Symfony 7.3 has invokable commands, arguments come as parameters with attributes: ...

May 30, 2025 · 2 min · Murat Useinov

Native lazy objects, the ORM view

Open the Doctrine proxy folder of any project older than a year. A generated subclass for every entity, getters overridden, one file per class in the cache dir. It worked for fifteen years, and the seams always showed: final classes, public typed properties, instanceof surprises, a folder of code you pretend not to see. PHP 8.4 lazy objects were designed with exactly this in mind. The native way needs no generated class: ...

April 9, 2025 · 2 min · Murat Useinov

Observability has a budget

Last year we turned tracing on everywhere. This year the telemetry invoice is a line item a manager asks about by name. The dashboards did not get more useful. More data, same confusion at 2 a.m. Time to design instead of collect. The design starts from the questions I actually ask when the pager goes off. In practice there are four: is the latency of key endpoints normal, did the error rate jump, how far behind are the queues, is the database saturated. That is the core set. Metrics for these must be cheap, always on, with alerts. Everything else can be sampled traces I pull up on demand. ...

March 10, 2025 · 2 min · Murat Useinov

Laravel 12 is a boring release

Laravel 12 came out on Monday. Tuesday evening I upgraded one mid-size project. The whole diff is composer constraints and the lock file. Tests green, deployed before midnight. People in chats are disappointed. Where are the big features. A major that mostly refreshes dependencies and starter kits, is it even a major. I remember 4.2 to 5.0. New folder structure, new config system, half of the packages dead on arrival. We planned it as a separate project, because it was one. Skip a major, and the next one cost double. ...

February 26, 2025 · 2 min · Murat Useinov