<?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>Symfony on Murat Useinov</title>
    <link>https://useinov.com/tags/symfony/</link>
    <description>Recent content in Symfony on Murat Useinov</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Mon, 25 May 2026 21:25:00 +0300</lastBuildDate>
    <atom:link href="https://useinov.com/tags/symfony/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>A worker is not a controller</title>
      <link>https://useinov.com/posts/2026/worker-not-controller/</link>
      <pubDate>Mon, 25 May 2026 21:25:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2026/worker-not-controller/</guid>
      <description>&lt;p&gt;All the logic inside &lt;code&gt;execute()&lt;/code&gt;, exit code always zero, no lifecycle thinking at all. A controller without a request. I still see this console command in every second codebase. Symfony stopped being only an HTTP framework a long time ago, but habits are slower than releases, and with 8.1 giving console and workers first-class attention the excuse is gone.&lt;/p&gt;
&lt;p&gt;An HTTP request lives for milliseconds. The kernel builds services, handles, throws everything away. A worker lives for hours. Same container, very different lifecycle. Every service that quietly keeps state, an in-memory cache, an accumulating buffer, an entity manager full of tracked objects, is invisible in HTTP and becomes a memory leak in a worker. If your consumer needs a nightly restart by cron, this is where the night went.&lt;/p&gt;</description>
    </item>
    <item>
      <title>The November upgrade train</title>
      <link>https://useinov.com/posts/2025/november-upgrade-train/</link>
      <pubDate>Fri, 21 Nov 2025 22:30:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2025/november-upgrade-train/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;From 8.5 I care about three things. The pipe operator makes transformation chains read left to right:&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;nv&#34;&gt;$slug&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$title&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;o&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;trim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#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;o&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;strtolower&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#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;o&#34;&gt;|&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fn&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;preg_replace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;/\s+/&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$s&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;The new URI extension puts standards-correct URL parsing in core. &lt;code&gt;parse_url&lt;/code&gt; had opinions instead of a specification, I will not miss it. And &lt;code&gt;#[\NoDiscard]&lt;/code&gt; 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.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony 7.3 and the code I get to delete</title>
      <link>https://useinov.com/posts/2025/delete-your-helpers/</link>
      <pubDate>Fri, 30 May 2025 15:25:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2025/delete-your-helpers/</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Two examples from one project.&lt;/p&gt;
&lt;p&gt;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:&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>Scheduler component, or cron as messages</title>
      <link>https://useinov.com/posts/2023/symfony-scheduler/</link>
      <pubDate>Wed, 31 May 2023 20:20:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2023/symfony-scheduler/</guid>
      <description>&lt;p&gt;&lt;code&gt;crontab -l&lt;/code&gt; under a login nobody remembers creating. That is where the real business logic of one server lived: a nightly cleanup, an export, a retry script.&lt;/p&gt;
&lt;p&gt;Symfony 6.3 came out yesterday with a Scheduler component. Periodic tasks defined in PHP, executed as Messenger messages. Experimental, but the idea deserves a note.&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;p&#34;&gt;#[&lt;/span&gt;&lt;span class=&#34;nd&#34;&gt;AsSchedule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;default&amp;#39;&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;k&#34;&gt;final&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;MainSchedule&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;implements&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ScheduleProviderInterface&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;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;getSchedule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Schedule&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;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Schedule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;add&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;nx&#34;&gt;RecurringMessage&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;every&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;10 minutes&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;CleanupExpiredCarts&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;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;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;Then &lt;code&gt;messenger:consume scheduler_default&lt;/code&gt;, and a worker fires the messages.&lt;/p&gt;</description>
    </item>
    <item>
      <title>DI attributes: config moves into the class</title>
      <link>https://useinov.com/posts/2023/symfony-di-attributes/</link>
      <pubDate>Mon, 27 Mar 2023 23:10:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2023/symfony-di-attributes/</guid>
      <description>&lt;p&gt;Three lines of YAML in a file two directories away, for one integer that one service reads. That was the first thing I converted when I started moving a project&amp;rsquo;s service config from YAML into attributes, to see where the line is.&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;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;__construct&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;p&#34;&gt;#[&lt;/span&gt;&lt;span class=&#34;nd&#34;&gt;Autowire&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;%env(int:IMPORT_BATCH_SIZE)%&amp;#39;&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;k&#34;&gt;private&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$batchSize&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;p&#34;&gt;)&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;Now the class tells you everything about itself. Same with &lt;code&gt;#[TaggedIterator]&lt;/code&gt; for collecting all implementations of an interface, and &lt;code&gt;#[AsDecorator]&lt;/code&gt; for wrapping a service. These are local facts. A local fact belongs next to the code it describes. When I open the class, I want to stop searching.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Argument resolvers and the HTTP border</title>
      <link>https://useinov.com/posts/2022/argument-resolver-dto/</link>
      <pubDate>Mon, 13 Jun 2022 20:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2022/argument-resolver-dto/</guid>
      <description>&lt;p&gt;Forty lines of &lt;code&gt;$request-&amp;gt;get()&lt;/code&gt; before the first line of real logic. That was the controller nobody wanted to touch, and the Symfony 6.1 upgrade was my excuse to open it.&lt;/p&gt;
&lt;p&gt;An action that reads ten request fields by hand does two jobs. It translates HTTP into data, and it runs the use case. The first job is boring and repeated in every action, which is exactly why it should not be written by hand ten times. Symfony has argument resolvers for this since 3.1. People just do not use them for their own types.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Retry is a strategy, not a loop</title>
      <link>https://useinov.com/posts/2022/messenger-retry-strategy/</link>
      <pubDate>Wed, 27 Apr 2022 20:20:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2022/messenger-retry-strategy/</guid>
      <description>&lt;p&gt;A failed job that retries every second, one hundred times, against a payment provider that is already down. That is a small DDoS with a queue in front of it. If the provider needs five minutes to recover, hammering it for the first two achieves nothing except log volume.&lt;/p&gt;
&lt;p&gt;Symfony Messenger has everything needed. You just have to configure it on purpose:&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-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;framework&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;messenger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;transports&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;async&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;dsn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;%env(MESSENGER_TRANSPORT_DSN)%&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;retry_strategy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;max_retries&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;delay&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;2000&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;multiplier&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;failure_transport&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;failed&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Two seconds, eight, thirty two, and so on. Exponential backoff gives the external service room to breathe. I also add jitter in a custom retry strategy, because a hundred messages that failed together will otherwise retry together, in one synchronized wave. Same failure, five times.&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>Attributes in Symfony 5.3</title>
      <link>https://useinov.com/posts/2021/symfony-attributes/</link>
      <pubDate>Fri, 04 Jun 2021 21:05:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2021/symfony-attributes/</guid>
      <description>&lt;p&gt;&lt;code&gt;#[Route(&#39;/orders/{id}&#39;)]&lt;/code&gt; right above the method, and nothing in &lt;code&gt;config/routes&lt;/code&gt;. Symfony 5.3 came out last week, and with PHP 8 attributes configuration finally found its place. Routes, autowiring hints, some validation, native syntax next to the code:&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;p&#34;&gt;#[&lt;/span&gt;&lt;span class=&#34;nd&#34;&gt;Route&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;/orders/{id}&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;methods&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;GET&amp;#39;&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;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;show&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nd&#34;&gt;Response&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;c1&#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;I spent years defending YAML routing. The argument was separation: code is code, wiring is wiring, one file shows the whole URL map. The argument was never wrong. It lost to practice. In every real project the first thing you do with a route is jump to the controller, and the first thing you do with a controller is wonder which route hits it. Two files, one mental join, forever. Attributes remove the join. Rename a method, the metadata moves with it. Delete the class, no orphaned YAML block stays behind to confuse the next person.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony Runtime: front controller as a callable</title>
      <link>https://useinov.com/posts/2021/symfony-runtime/</link>
      <pubDate>Fri, 16 Apr 2021 23:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2021/symfony-runtime/</guid>
      <description>&lt;p&gt;&lt;code&gt;public/index.php&lt;/code&gt;, unchanged since I learned Symfony. Create the request from globals, run the kernel, send the response, terminate. Symfony 5.3 arrives next month, and the Runtime component rewrites this file. It looks like a small refactoring. It is a statement about where PHP is going.&lt;/p&gt;
&lt;p&gt;Baked into those few lines is one big assumption: one process, one request, then we die. FPM made the assumption true for fifteen years, so nobody saw it as an assumption.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Attributes: metadata moves in with the code</title>
      <link>https://useinov.com/posts/2020/attributes-near-code/</link>
      <pubDate>Fri, 18 Dec 2020 12:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2020/attributes-near-code/</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;p&#34;&gt;#[&lt;/span&gt;&lt;span class=&#34;nd&#34;&gt;Route&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;/orders/{id}&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;methods&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;GET&amp;#39;&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;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;show&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nd&#34;&gt;Response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Symfony 5.2 accepts this on PHP 8, and the annotation era quietly starts to end. Same shape as the annotation, but now it is language, not a comment. The engine parses it, static analysis sees it, a typo is a compile-time complaint instead of a route that silently does not exist. Doctrine and the validator are heading the same way. Everything that lived in docblocks will move over the next year or two.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Types are for reading, not for the compiler</title>
      <link>https://useinov.com/posts/2020/typed-code-navigation/</link>
      <pubDate>Sat, 27 Jun 2020 22:20:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2020/typed-code-navigation/</guid>
      <description>&lt;p&gt;&lt;code&gt;OrderRepository&lt;/code&gt; in a constructor. Click. I am there. That is how I explore a big Symfony project now, and I noticed my main reason for types has changed. Not bug catching anymore. Navigation.&lt;/p&gt;
&lt;p&gt;Symfony 5.1 is out, PHP 7.4 is everywhere I work, and the IDE knows every caller and every implementation. Compare with the array-passing style we all wrote for years:&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;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;register&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$data&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;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;c1&#34;&gt;// what is in $data? read three call sites to find out
&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;versus&lt;/p&gt;</description>
    </item>
    <item>
      <title>Waiting for three APIs, one at a time</title>
      <link>https://useinov.com/posts/2020/parallel-http-requests/</link>
      <pubDate>Tue, 21 Apr 2020 21:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2020/parallel-http-requests/</guid>
      <description>&lt;p&gt;Three external calls on one page: prices, stock, delivery estimate. Each answers in about 300 ms. The page waits a full second, because we call them one after another. PHP is synchronous, what can you do.&lt;/p&gt;
&lt;p&gt;Turns out, something. Symfony HttpClient is lazy. &lt;code&gt;request()&lt;/code&gt; sends and returns immediately. The waiting happens when you read the response. So start all three, read later:&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;nv&#34;&gt;$prices&lt;/span&gt;   &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$client&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$pricesUrl&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;nv&#34;&gt;$stock&lt;/span&gt;    &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$client&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$stockUrl&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;nv&#34;&gt;$delivery&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$client&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$deliveryUrl&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&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;$data&lt;/span&gt; &lt;span class=&#34;o&#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;s1&#34;&gt;&amp;#39;prices&amp;#39;&lt;/span&gt;   &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$prices&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;toArray&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;s1&#34;&gt;&amp;#39;stock&amp;#39;&lt;/span&gt;    &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$stock&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;toArray&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;s1&#34;&gt;&amp;#39;delivery&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$delivery&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;toArray&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;p&#34;&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Under the hood it is curl multi. Three requests fly at once, wall time is the slowest one instead of the sum. Our second became 350 ms. No swoole, no reactphp, no async rewrite. Same boring controller.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Cleaning up before Symfony 5</title>
      <link>https://useinov.com/posts/2019/symfony5-deprecations/</link>
      <pubDate>Wed, 25 Sep 2019 10:25:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2019/symfony5-deprecations/</guid>
      <description>&lt;p&gt;Symfony 5.0 is 4.4 minus everything deprecated. That one sentence is the whole upgrade plan. Both land in November, and if the application runs on 4.4 with zero deprecation warnings, the major is a version bump. Two months is enough to get there in small chunks, with no freeze and no heroic branch that lives for six weeks.&lt;/p&gt;
&lt;p&gt;The order that works for me.&lt;/p&gt;
&lt;p&gt;First, get to 4.3 and make the deprecation report visible. The PHPUnit bridge prints the summary after the test run. In dev, the profiler collects the same warnings per request. You cannot clean what you do not see. The first report will be depressing. Fine. It is a todo list, not a verdict.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Messenger retries and the failure transport</title>
      <link>https://useinov.com/posts/2019/messenger-failure-queue/</link>
      <pubDate>Thu, 30 May 2019 23:25:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2019/messenger-failure-queue/</guid>
      <description>&lt;p&gt;A log full of the same stack trace, every second, all night. That is what a queue looks like when a handler throws on a malformed payload and the retry policy is &amp;ldquo;forever&amp;rdquo;. Symfony 4.3 is out this week, and Messenger in it finally has an answer for that night.&lt;/p&gt;
&lt;p&gt;Retry is good for transient errors. Network blinked, deadlock, remote API returned 503. Wait, try again, it passes. But some errors are permanent. Malformed payload, entity deleted, a bug in the handler. Retrying those forever means a worker grinding the same poison message until morning. Infinite retry is not persistence, it is denial.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Deprecations are a to-do list, not noise</title>
      <link>https://useinov.com/posts/2018/deprecation-budget/</link>
      <pubDate>Fri, 30 Nov 2018 15:15:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2018/deprecation-budget/</guid>
      <description>&lt;p&gt;&lt;code&gt;SYMFONY_DEPRECATIONS_HELPER=&#39;max[total]=20&#39; ./bin/phpunit&lt;/code&gt;. Twenty is the number of deprecation warnings we had on the day we started counting. The rule is that it only goes down.&lt;/p&gt;
&lt;p&gt;Symfony 4.2 came out yesterday, on the six month schedule. With this cadence deprecation warnings stop being an event and become weather. Every minor release brings a new batch, teams train themselves not to see them, and that is a mistake, because a deprecation is the next major upgrade delivered early, in small pieces, with instructions.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Messenger: in-process today, queue tomorrow</title>
      <link>https://useinov.com/posts/2018/messenger-sync-first/</link>
      <pubDate>Tue, 19 Jun 2018 22:50:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2018/messenger-sync-first/</guid>
      <description>&lt;p&gt;&lt;code&gt;$bus-&amp;gt;dispatch(new SendWelcomeEmail($user-&amp;gt;getId()));&lt;/code&gt; and then, in the same request, a handler:&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;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;SendWelcomeEmailHandler&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;k&#34;&gt;public&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;fm&#34;&gt;__invoke&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;SendWelcomeEmail&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$message&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;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;c1&#34;&gt;// load user by id, send the email
&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;That is Messenger in Symfony 4.1, still marked experimental. By default everything is synchronous, so at first it is a function call with extra steps.&lt;/p&gt;
&lt;p&gt;The extra steps are for the transport. Change configuration, route this message class to AMQP, and the same handler runs in a worker process. The calling code does not change. Start synchronous, go async when you need it. I like this order much more than &amp;ldquo;install RabbitMQ on day one&amp;rdquo;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>.env is not a secrets store</title>
      <link>https://useinov.com/posts/2018/env-not-secrets/</link>
      <pubDate>Wed, 14 Mar 2018 15:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2018/env-not-secrets/</guid>
      <description>&lt;p&gt;A &lt;code&gt;.env&lt;/code&gt; file committed to git, &amp;ldquo;temporarily&amp;rdquo;. Database password, API keys, mailer credentials, all in one file. Symfony 4 moved configuration to environment variables, and this is the second project this month where I see the same thing.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; is a developer convenience. It exists so local setup does not require exporting fifteen variables by hand before running the app. That is the whole job of this file. &lt;code&gt;.env.dist&lt;/code&gt; goes to git with placeholder values, &lt;code&gt;.env&lt;/code&gt; stays in &lt;code&gt;.gitignore&lt;/code&gt; with your local ones. This part is not negotiable.&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>Private services: the container steps back</title>
      <link>https://useinov.com/posts/2017/private-services/</link>
      <pubDate>Tue, 30 May 2017 15:15:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2017/private-services/</guid>
      <description>&lt;p&gt;&lt;code&gt;$container-&amp;gt;get(&#39;app.mailer&#39;)&lt;/code&gt; stopped working this week. Symfony 3.3 is out, and in the new configuration services are private by default. The container refuses the call. Everyone is writing about autowiring. This is the change I like more.&lt;/p&gt;
&lt;p&gt;That call was always a smell. A class that pulls dependencies from the container by string id is a class with secrets. Its constructor says nothing, and to test it you boot half the framework, because it may ask for anything at any moment.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Autowiring is coming to Symfony 3.3</title>
      <link>https://useinov.com/posts/2017/symfony-autowiring/</link>
      <pubDate>Wed, 08 Mar 2017 15:25:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2017/symfony-autowiring/</guid>
      <description>&lt;p&gt;Our services file repeats every class name three times. Once as the id, once as the class, and again in the arguments of every service that needs it. Add a constructor parameter, edit YAML, clear cache, repeat. Symfony 3.3 lands in May and promises to delete most of that file:&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-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;_defaults&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;autowire&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;autoconfigure&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#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;w&#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;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;AppBundle\&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#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;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;resource&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;../../src/AppBundle/*&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Autowiring and autoconfiguration on by default, the whole source folder registered in five lines. I have mixed feelings. Sorted, they look like this.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Order status is a graph</title>
      <link>https://useinov.com/posts/2016/order-status-graph/</link>
      <pubDate>Wed, 30 Nov 2016 22:10:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2016/order-status-graph/</guid>
      <description>&lt;p&gt;&lt;code&gt;if ($order-&amp;gt;status == &#39;paid&#39;)&lt;/code&gt; in the shipping module. Another one in the refund module. A slightly different one in the admin panel. Then someone cancels a shipped order from an admin button, because the button just writes the string, and the warehouse learns about it a week later.&lt;/p&gt;
&lt;p&gt;Symfony 3.2 is out this week and ships a Workflow component. Finally a framework says out loud what every order table has been whispering for years: status is a state machine, not a string column you assign wherever convenient.&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>Symfony 3.0 is not a rewrite</title>
      <link>https://useinov.com/posts/2015/symfony-two-step/</link>
      <pubDate>Mon, 30 Nov 2015 22:05:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2015/symfony-two-step/</guid>
      <description>&lt;p&gt;Symfony 2.8 and 3.0, released today, both at once, on purpose. The pairing is the whole message. 2.8 is the last of the 2.x line and an LTS. 3.0 is roughly 2.8 with the deprecated code deleted. Same features, cleaner body.&lt;/p&gt;
&lt;p&gt;This turns a scary major upgrade into two steps.&lt;/p&gt;
&lt;p&gt;Step one, move to 2.8. Minor upgrade, low risk. Then make the deprecation log empty. The phpunit bridge prints every deprecated call your code and tests touch. Burn the list down during normal sprints, item by item, each fix small and releasable on its own.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony 2.7: LTS is the feature</title>
      <link>https://useinov.com/posts/2015/symfony-lts/</link>
      <pubDate>Sun, 31 May 2015 12:35:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2015/symfony-lts/</guid>
      <description>&lt;p&gt;Three years of bug fixes. Four years of security fixes. Symfony 2.7 came out this weekend, and that is the line from the announcement I care about. The changelog has new things in it, but the important word is on the label: LTS.&lt;/p&gt;
&lt;p&gt;For a pet project this is boring. For a product with paying users and a team, this is the actual feature.&lt;/p&gt;
&lt;p&gt;A big application does not upgrade for fun. Every framework upgrade is testing time, regression risk, and a sprint that produces nothing visible. Business asks a fair question: what do we get. &amp;ldquo;Newer version&amp;rdquo; is not an answer. &amp;ldquo;We keep receiving security fixes until 2018 without touching anything&amp;rdquo; is.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony 2.6: dump() and boring upgrades</title>
      <link>https://useinov.com/posts/2014/symfony-26-vardumper/</link>
      <pubDate>Thu, 18 Dec 2014 22:40:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2014/symfony-26-vardumper/</guid>
      <description>&lt;p&gt;&lt;code&gt;dump()&lt;/code&gt; instead of &lt;code&gt;var_dump()&lt;/code&gt;. Symfony 2.6 came out at the end of November, and this is the visible gift: VarDumper. Collapsible output, clickable class names, works inside the toolbar. A small thing, used fifty times a day. Tools like this improve life more than big features do.&lt;/p&gt;
&lt;p&gt;But the important story in 2.6 is not a feature. The release notes read like a preparation checklist for Symfony 3: deprecations, everywhere.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Symfony best practices: defaults, not laws</title>
      <link>https://useinov.com/posts/2014/symfony-best-practices/</link>
      <pubDate>Sun, 14 Dec 2014 20:30:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2014/symfony-best-practices/</guid>
      <description>&lt;p&gt;One AppBundle. That is the line in the official Symfony Best Practices book I read twice. For years the answer to &amp;ldquo;how many bundles&amp;rdquo; was &amp;ldquo;as many as you have features&amp;rdquo;, and here the framework&amp;rsquo;s own book says: one.&lt;/p&gt;
&lt;p&gt;The book came out this autumn. Worth reading even if you are not on Symfony, because the interesting part is the change of tone.&lt;/p&gt;
&lt;p&gt;The old Symfony way: everything is a bundle. Your application is bundles, reusable, configurable, with their own extensions and semantic configuration. Very flexible. And for a normal business application, mostly ceremony. You write a configuration class for code that will never leave this one project. I wrote such classes. Three of them. None was ever reused.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Services instead of fat controllers</title>
      <link>https://useinov.com/posts/2014/services-instead-of-fat-controllers/</link>
      <pubDate>Tue, 02 Dec 2014 22:20:00 +0300</pubDate>
      <guid>https://useinov.com/posts/2014/services-instead-of-fat-controllers/</guid>
      <description>&lt;p&gt;120 lines in one action. Order confirmation: load the order, charge the card, send the email, render the page. It ran fine for a year. Then a console script needed the same operation, and the action turned out to be glued to SMTP, to the database and to the HTTP request. All at once.&lt;/p&gt;
&lt;p&gt;Symfony people talk about dependency injection so much it sounds like religion. It is one simple idea: a class receives its dependencies and does not create them.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
