<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Composer on Murat Useinov</title>
    <link>https://useinov.com/tags/composer/</link>
    <description>Recent content in Composer on Murat Useinov</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Fri, 27 Feb 2026 19:45:00 +0300</lastBuildDate>
    <atom:link href="https://useinov.com/tags/composer/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Dependency upgrades without a big bang</title>
      <link>https://useinov.com/posts/2026/small-dependency-upgrades/</link>
      <pubDate>Fri, 27 Feb 2026 19:45:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2026/small-dependency-upgrades/</guid>
      <description>&lt;p&gt;A &lt;code&gt;composer.lock&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;The alternative is dull and it works. Upgrades as normal operations, like backups.&lt;/p&gt;</description>
    </item>
    <item>
      <title>vendor/autoload.php is not free</title>
      <link>https://useinov.com/posts/2025/autoload-not-free/</link>
      <pubDate>Wed, 20 Aug 2025 20:10:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2025/autoload-not-free/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;require vendor/autoload.php&lt;/code&gt; 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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony 7.1 and the value of boring releases</title>
      <link>https://useinov.com/posts/2024/symfony-seven-one/</link>
      <pubDate>Fri, 31 May 2024 12:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2024/symfony-seven-one/</guid>
      <description>&lt;p&gt;&lt;code&gt;composer update&lt;/code&gt;, 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>PHP 8.3 and Symfony 7 in one month</title>
      <link>https://useinov.com/posts/2023/november-baselines/</link>
      <pubDate>Thu, 30 Nov 2023 10:45:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2023/november-baselines/</guid>
      <description>&lt;p&gt;&lt;code&gt;public const int TIMEOUT_SECONDS = 30;&lt;/code&gt; One week old, and the one thing I waited for from PHP 8.3.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;json_validate()&lt;/code&gt;, which checks JSON syntax without building the whole tree in memory. Everyone had a helper that did &lt;code&gt;json_decode&lt;/code&gt; and threw the result away. Now the helper can retire.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Before Laravel 10: raise the baseline first</title>
      <link>https://useinov.com/posts/2023/laravel-upgrade-baseline/</link>
      <pubDate>Sat, 07 Jan 2023 22:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2023/laravel-upgrade-baseline/</guid>
      <description>&lt;p&gt;&lt;code&gt;composer why-not php 8.1&lt;/code&gt;. One command, and it tells you who holds you back. Run it before making any plans.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony 6.0 is boring, take the compliment</title>
      <link>https://useinov.com/posts/2021/symfony6-via-54/</link>
      <pubDate>Thu, 09 Dec 2021 22:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2021/symfony6-via-54/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>PHP 8 in production: dependencies first</title>
      <link>https://useinov.com/posts/2021/php8-dependencies-first/</link>
      <pubDate>Fri, 08 Jan 2021 23:20:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2021/php8-dependencies-first/</guid>
      <description>&lt;p&gt;&lt;code&gt;composer why-not php 8.0&lt;/code&gt;. That is the first command of the migration, and it has nothing to do with syntax.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony 4: the framework as recipes</title>
      <link>https://useinov.com/posts/2017/symfony-flex/</link>
      <pubDate>Thu, 30 Nov 2017 21:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2017/symfony-flex/</guid>
      <description>&lt;p&gt;A kernel, an &lt;code&gt;index.php&lt;/code&gt;, 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.&lt;/p&gt;
&lt;p&gt;The Standard Edition is gone. You start from the empty skeleton and ask for what you need. &lt;code&gt;composer require orm&lt;/code&gt; pulls Doctrine, and the recipe writes the config files, registers the bundle in &lt;code&gt;bundles.php&lt;/code&gt;, adds the variables to &lt;code&gt;.env&lt;/code&gt;. You watch your own repo grow file by file as you add dependencies.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Laravel 5.5 and package auto-discovery</title>
      <link>https://useinov.com/posts/2017/laravel-auto-discovery/</link>
      <pubDate>Thu, 31 Aug 2017 15:45:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2017/laravel-auto-discovery/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The feature in every screenshot is package auto-discovery. A package declares its service provider in its own &lt;code&gt;composer.json&lt;/code&gt;, and after &lt;code&gt;composer require&lt;/code&gt; it is registered. No editing of &lt;code&gt;config/app.php&lt;/code&gt;, no provider line copied from the readme.&lt;/p&gt;
&lt;p&gt;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 &lt;code&gt;composer require&lt;/code&gt; is the whole ceremony. The list of code that runs at boot lives in &lt;code&gt;vendor/&lt;/code&gt;, spread over other people&amp;rsquo;s files. &lt;code&gt;php artisan package:discover&lt;/code&gt; shows it, and &lt;code&gt;dont-discover&lt;/code&gt; in composer.json switches it off per package. But the default flipped from explicit to implicit.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Multi-stage builds for PHP images</title>
      <link>https://useinov.com/posts/2017/docker-multi-stage/</link>
      <pubDate>Fri, 07 Jul 2017 18:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2017/docker-multi-stage/</guid>
      <description>&lt;p&gt;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 &lt;code&gt;composer install&lt;/code&gt; 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&amp;rsquo;s glue was different.&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony by the piece</title>
      <link>https://useinov.com/posts/2016/symfony-by-piece/</link>
      <pubDate>Tue, 10 May 2016 19:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2016/symfony-by-piece/</guid>
      <description>&lt;p&gt;A folder of cron scripts, each one a &lt;code&gt;.php&lt;/code&gt; file with hand-parsed &lt;code&gt;$argv&lt;/code&gt;. That is what CLI looked like on our legacy Kohana application until last month. One line fixed it:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;composer require symfony/console
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now each script is a small Command class with named options, &lt;code&gt;--help&lt;/code&gt; for free, and exit codes that cron can actually check. The framework around it did not notice anything. Console does not care who serves your HTTP.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Composer in a legacy Kohana project</title>
      <link>https://useinov.com/posts/2015/kohana-meets-composer/</link>
      <pubDate>Thu, 22 Jan 2015 20:05:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2015/kohana-meets-composer/</guid>
      <description>&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;require&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;APPPATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;../vendor/autoload.php&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One line in &lt;code&gt;bootstrap.php&lt;/code&gt;. This month it became the whole strategy for a project we decided to keep on Kohana.&lt;/p&gt;
&lt;p&gt;The framework is quiet, the project is alive. These two facts have to coexist somehow. The plan: nothing new gets written the Kohana way. New dependencies come through Composer, and Kohana&amp;rsquo;s autoloader and Composer&amp;rsquo;s autoloader live together fine.&lt;/p&gt;
&lt;p&gt;First candidate was the HTTP client. We talk to two external APIs, and the code around &lt;code&gt;Request_Client_External&lt;/code&gt; was not pretty. Guzzle is better in every way. But Guzzle does not spread through the codebase. There is an interface:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Composer next to Kohana bootstrap</title>
      <link>https://useinov.com/posts/2014/composer-next-to-kohana/</link>
      <pubDate>Sun, 23 Nov 2014 21:10:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2014/composer-next-to-kohana/</guid>
      <description>&lt;p&gt;One line in &lt;code&gt;bootstrap.php&lt;/code&gt;, above &lt;code&gt;Kohana::init()&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-php&#34; data-lang=&#34;php&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;require&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;DOCROOT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;vendor/autoload.php&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is the whole integration. Kohana keeps its modules and its underscore autoloader. Composer brings Packagist. Two autoloaders in one process, and they do not collide: Composer packages use namespaces, Kohana classes have underscores, the lookups never meet.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;require&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;guzzlehttp/guzzle&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;~5.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This setup runs in production on one of my projects for a few months. Zero problems from the autoloading side. The problems, when they came, were from me.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
