Dependency upgrades without a big bang

A composer.lock that had not changed in three years. A colleague showed it to me last week, not as a confession, as a normal fact about the project. Nobody decided this. Every single upgrade looked small and risky, so everybody postponed it. Three years of postponed small risks equals one migration project with a budget and a name. The alternative is dull and it works. Upgrades as normal operations, like backups. ...

February 27, 2026 · 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

Symfony 7.1 and the value of boring releases

composer update, the changelog over coffee, two deprecation notices fixed, done before lunch. That was Symfony 7.1 on one project this morning. Nothing in the release will change your architecture, and I mean this as a compliment. There are nice small things. An attribute that maps an uploaded file straight into a controller argument, one more piece of request plumbing gone. A new experimental component for type introspection that libraries will quietly benefit from. A layer of deprecations preparing the next major. That is the whole show. ...

May 31, 2024 · 2 min · Murat Useinov

PHP 8.3 and Symfony 7 in one month

public const int TIMEOUT_SECONDS = 30; One week old, and the one thing I waited for from PHP 8.3. Busy November. 8.3 released last week, and yesterday Symfony shipped 6.4 LTS and 7.0 on the same day. Time to plan, not to jump. Typed class constants are a small thing, but constants were the last untyped island in a class, and a child class could silently redefine one to a different type. Also json_validate(), which checks JSON syntax without building the whole tree in memory. Everyone had a helper that did json_decode and threw the result away. Now the helper can retire. ...

November 30, 2023 · 2 min · Murat Useinov

Before Laravel 10: raise the baseline first

composer why-not php 8.1. One command, and it tells you who holds you back. Run it before making any plans. Laravel 10 comes next month and requires PHP 8.1. One project I help with is on Laravel 9 and PHP 8.0. The temptation is one heroic branch: new PHP, new framework, new package versions. When that branch breaks in production, you will not know which of the three changes broke it. ...

January 7, 2023 · 2 min · Murat Useinov

Symfony 6.0 is boring, take the compliment

The deprecation counter in the profiler, three digits. That is where one project stands this week, and that number is the whole Symfony 6 migration. Symfony shipped 5.4 and 6.0 together at the end of November, same code, same day. 4.4 and 5.0 worked exactly like this two years ago, so the story is rehearsed. 6.0 is 5.4 with the deprecated code deleted. You never migrate to a major. You migrate to the last minor, and the major is a formality. ...

December 9, 2021 · 2 min · Murat Useinov

PHP 8 in production: dependencies first

composer why-not php 8.0. That is the first command of the migration, and it has nothing to do with syntax. PHP 8.0 is six weeks old. Twitter is full of match expressions and constructor promotion. Meanwhile the real upgrade of a working project is a dependency problem. Your code is maybe twenty percent of what runs in production. The rest is the framework, thirty packages and a dozen extensions, and any one of them can be the blocker. ...

January 8, 2021 · 2 min · Murat Useinov

Symfony 4: the framework as recipes

A kernel, an index.php, a config folder with almost nothing in it. That is a Symfony 4 project on day one, and 4.0 came out today together with 3.4 LTS. The code changes are the smaller story. How a project starts is the bigger one. The Standard Edition is gone. You start from the empty skeleton and ask for what you need. composer require orm pulls Doctrine, and the recipe writes the config files, registers the bundle in bundles.php, adds the variables to .env. You watch your own repo grow file by file as you add dependencies. ...

November 30, 2017 · 2 min · Murat Useinov

Laravel 5.5 and package auto-discovery

Laravel 5.5 came out yesterday, an LTS: two years of bug fixes, three of security fixes. Good news for projects that outlive the hype cycle, which is most of them. The feature in every screenshot is package auto-discovery. A package declares its service provider in its own composer.json, and after composer require it is registered. No editing of config/app.php, no provider line copied from the readme. Convenient. Now look at what got deleted. Installing a package used to be two steps, and the second step was consent: a line in my own config saying this code runs inside my application at boot. Now composer require is the whole ceremony. The list of code that runs at boot lives in vendor/, spread over other people’s files. php artisan package:discover shows it, and dont-discover in composer.json switches it off per package. But the default flipped from explicit to implicit. ...

August 31, 2017 · 2 min · Murat Useinov

Multi-stage builds for PHP images

720 MB. That was our production PHP image, and most of it was composer, git, unzip, build headers and a pile of apt cache. None of it runs in production. It was there because composer install needs it, and a Dockerfile was one linear script. People worked around this with two Dockerfiles and a shell script gluing them. Ugly, and everyone’s glue was different. Docker 17.05 brought multi-stage builds. The first Docker feature in a while that fixes a problem I actually had. Now it is one file: ...

July 7, 2017 · 2 min · Murat Useinov