<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Grégoire Paris</title>
    <description>The latest articles on DEV Community by Grégoire Paris (@greg0ire).</description>
    <link>https://dev.to/greg0ire</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1699%2F657779.jpeg</url>
      <title>DEV Community: Grégoire Paris</title>
      <link>https://dev.to/greg0ire</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/greg0ire"/>
    <language>en</language>
    <item>
      <title>Wiremock + testcontainers + Algolia + Go = ❤️</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Fri, 20 Jun 2025 08:16:02 +0000</pubDate>
      <link>https://dev.to/manomano-tech-team/wiremock-testcontainers-algolia-go--3hn7</link>
      <guid>https://dev.to/manomano-tech-team/wiremock-testcontainers-algolia-go--3hn7</guid>
      <description>&lt;p&gt;When dealing with a SaaS like &lt;a href="https://www.algolia.com/" rel="noopener noreferrer"&gt;Algolia&lt;/a&gt;, testing can be a hassle. Ideally, you should not "mock what you do not own". In other words, you should not mock libraries such as the Algolia SDK, not just because it might evolve in unforeseen ways, but also because writing unit tests for a piece of code where the logic is dictated by something external to the code is not a good idea: you would not be testing the part that has the most complexity.&lt;/p&gt;

&lt;p&gt;To take a concrete example, let's imagine you want to index documents in Algolia. There is an end goal behind that, and the end goal is that it is possible to search for these documents.&lt;/p&gt;

&lt;p&gt;Ideally, you would have a Docker container running Algolia locally that would be super fast at indexing and use the same code your production Algolia app uses, but sadly that does not exist, and I'm not hopeful it ever will.&lt;/p&gt;

&lt;p&gt;In a legacy service I worked on, we have a test Algolia app that we use for integration tests. It worked great, but in the past years, Algolia introduced a new cloud-based architecture, and with this architecture, an indexing task can take a lot more time to be "published". As a result, using a test application on the cloud-based architecture is not an option anymore, as it slows the test suite down to a crawl. 🐌&lt;/p&gt;

&lt;p&gt;On a new project, I decided to re-evaluate my options, and remembered a tool that seems to be the next best thing for the job: &lt;a href="https://wiremock.org" rel="noopener noreferrer"&gt;Wiremock&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I will guide you through the process of setting Wiremock and testcontainers to test Algolia's own &lt;a href="https://www.algolia.com/doc/libraries/go/v4/" rel="noopener noreferrer"&gt;quickstart guide for Golang&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiremock: a VCR for HTTP
&lt;/h2&gt;

&lt;p&gt;Wiremock is a testing tool that comes with a so-called &lt;a href="https://wiremock.org/docs/record-playback" rel="noopener noreferrer"&gt;"record and&lt;br&gt;
playback"&lt;/a&gt; feature.&lt;/p&gt;

&lt;p&gt;It means you can do this once in your local environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────┐          ┌────────────────────────────┐       ┌─────────┐
│            ├─────────►│                            ├──────►│         │
│Your service│          │ Wiremock in recording mode │       │ Algolia │
│            │◄─────────┤                            │◄──────┤         │
└────────────┘          └────────────────────────────┘       └─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In recording mode, you give Wiremock a URL to record, and it will store files representing the requests you made, and the corresponding responses. With Algolia, it can be quite long, especially if you &lt;a href="https://www.algolia.com/doc/api-reference/api-methods/wait-task" rel="noopener noreferrer"&gt;wait for operations&lt;/a&gt;.&lt;br&gt;
What happens in practice is that the SDK will use a polling mechanism to check if your task is published. This will result in a lot of similarly looking files.&lt;br&gt;
This is not very interesting to reproduce in your test, so I recommend simply deleting files representing a negative response to the question: "are the changes published yet?". Those typically contain a JSON field called &lt;code&gt;status&lt;/code&gt; set to &lt;code&gt;notPublished&lt;/code&gt; in their body, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"notPublished"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pendingTask"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the file is published, this becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"published"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pendingTask"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The files have names that are a bit ugly, so I usually rename them for clarity.&lt;br&gt;
For example, you might rename &lt;code&gt;1_indexes_test-index_task_226434943725-6e8689fa-9bbb-43fb-9d24-6824c02fc7d5.json&lt;/code&gt;&lt;br&gt;
to &lt;code&gt;index_test_task_published.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once your recording is done, you can run your tests like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────┐          ┌───────────────────────────┐
│            ├─────────►│                           │
│Your service│          │ Wiremock in playback mode │
│            │◄─────────┤                           │
└────────────┘          └───────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In playback mode, Wiremock will respond to your request with the mappings it has stored previously, and pretend to be Algolia. 🥸&lt;/p&gt;

&lt;p&gt;While this does not shield you against breaking changes in the Algolia HTTP API, it does come with a few advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It shields you against breaking changes or bugs in the Algolia SDK.&lt;/li&gt;
&lt;li&gt;You no longer have to mock the SDK, which is a bad practice and a pain to do. A consequence of that is that your tests become easier to understand, and more expressive, and that they check things at a higher level rather than focusing on implementation details.&lt;/li&gt;
&lt;li&gt;It still means that at least once, you do run the tests against the real thing, so if there is some issue that can only be detected at runtime, you will know about it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wiremock is a java application, but that shouldn't matter too much, especially given there is an &lt;a href="https://hub.docker.com/r/wiremock/wiremock" rel="noopener noreferrer"&gt;official Docker image&lt;/a&gt; you can use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testcontainers: Docker for your tests
&lt;/h2&gt;

&lt;p&gt;At ManoMano, we use Gitlab CI. While it is possible to define &lt;a href="https://docs.gitlab.com/ci/services/" rel="noopener noreferrer"&gt;a Gitlab CI service&lt;/a&gt; with the aforementioned Docker image, that's not a great solution because Gitlab services do not expose the full power of Docker. For instance, mounting a volume is not possible, probably not without heavy involvement of privileged users.&lt;/p&gt;

&lt;p&gt;A great alternative is &lt;a href="https://testcontainers.com" rel="noopener noreferrer"&gt;testcontainers&lt;/a&gt; + &lt;a href="https://testcontainers.com/cloud/" rel="noopener noreferrer"&gt;testcontainers Cloud&lt;/a&gt;. Testcontainers is a library available in many languages that allows you to start and stop Docker containers during your tests, making it possible to get good isolation between tests.&lt;br&gt;
Testcontainers Cloud is a service that allows you to run said containers on a remote infrastructure, as opposed to running them on your own infrastructure, which, if you want to use Kubernetes runners for Gitlab, implies using Docker in Docker, which is not great from the security standpoint.&lt;br&gt;
Locally, you would still use a local docker container, but in the CI,&lt;br&gt;
tescontainers will send requests to testcontainers cloud, to start and stop containers. Enough unpaid endorsement, let's get to the code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo time
&lt;/h2&gt;

&lt;p&gt;Let us follow &lt;a href="https://www.algolia.com/doc/libraries/go/v4/" rel="noopener noreferrer"&gt;Algolia's quickstart guide for Golang&lt;/a&gt; and see how easy it is to&lt;br&gt;
test.&lt;/p&gt;

&lt;p&gt;If you want to follow along, install &lt;a href="https://mise.jdx.dev/getting-started.html" rel="noopener noreferrer"&gt;mise-en-place&lt;/a&gt; and let's go!&lt;/p&gt;

&lt;p&gt;For the sake of brevity, I will not systematically show the entirety of a file I edit in all snippets, however I have tried to create one commit per step in &lt;a href="https://github.com/greg0ire/wiremock-blogpost/commits" rel="noopener noreferrer"&gt;this Github repository&lt;/a&gt;, in case you would like to play with the code or simply read it in your own editor.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installing Go
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mise use go@1.24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Creating a new project
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go mod init algolia-wiremock-testcontainers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Installing the Algolia SDK
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go get github.com/algolia/algoliasearch-client-go/v4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Setting up the environment
&lt;/h3&gt;

&lt;p&gt;At this point, you will need to set up a test Algolia application. Once you are done, you should have an application ID and an API key.&lt;/p&gt;

&lt;p&gt;Let us use an unversioned env file to store our credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# mise.toml&lt;/span&gt;
&lt;span class="nn"&gt;[env]&lt;/span&gt;
&lt;span class="py"&gt;_.file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;".env"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env&lt;/span&gt;
&lt;span class="nv"&gt;ALGOLIA_APP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;changeme
&lt;span class="nv"&gt;ALGOLIA_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;changeme
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will need to replace &lt;code&gt;ALGOLIA_APP_ID&lt;/code&gt; and &lt;code&gt;ALGOLIA_API_KEY&lt;/code&gt; with values from &lt;a href="https://dashboard.algolia.com/account/api-keys" rel="noopener noreferrer"&gt;your account&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .gitignore&lt;/span&gt;
/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Writing the code to be tested
&lt;/h3&gt;

&lt;p&gt;Let us take the code from Algolia's quickstart guide and split it into two files:&lt;/p&gt;

&lt;p&gt;First, we have the code under test where the only changes are getting the environment variables from the actual environment, and renaming packages and functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// indexer.go&lt;/span&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;indexer&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/search"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;indexRecord&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Get Algolia credentials from environment variables&lt;/span&gt;
    &lt;span class="n"&gt;appID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_APP_ID"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;indexName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"test-index"&lt;/span&gt;

    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"objectID"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"object-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"test record"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Create a new Algolia client&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Add record to an index&lt;/span&gt;
    &lt;span class="n"&gt;saveResp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SaveObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSaveObjectRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Wait until indexing is done&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitForTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saveResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TaskID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make it work, you will need to install the Algolia SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go get github.com/algolia/algoliasearch-client-go/v4
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go mod tidy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That call to &lt;code&gt;WaitForTask&lt;/code&gt; is what is going to take the most time, and a good reason not to use a real Algolia instance in your test suite. That's what we are going to try first though.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing the test with a real Algolia instance
&lt;/h3&gt;

&lt;p&gt;Let's start simple and write a first version of the test that talks directly to Algolia:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// indexer_test.go&lt;/span&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;indexer&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/debug"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/search"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestIndexRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// helps with seeing the progress, since this is super long&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;appID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_APP_ID"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;indexName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"test-index"&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create client: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Create index indirectly by setting settings&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSetSettingsRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"test-index"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEmptyIndexSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSearchableAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to set settings: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Ensure the test index is deleted after the test completes&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiDeleteIndexRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to delete index: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c"&gt;// Call the function under test&lt;/span&gt;
    &lt;span class="c"&gt;// this should index a record in Algolia&lt;/span&gt;
    &lt;span class="n"&gt;indexRecord&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Verify the record was indexed by searching for it&lt;/span&gt;
    &lt;span class="c"&gt;// This search should return record with "test" in their contents&lt;/span&gt;
    &lt;span class="c"&gt;// The quickstart guide currently uses a more complex version of this&lt;/span&gt;
    &lt;span class="n"&gt;searchResp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchSingleIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSearchSingleIndexRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;WithSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchParamsObjectAsSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEmptySearchParamsObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                        &lt;span class="n"&gt;SetQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Assert that there are hits&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No hits found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;aaaaand that doesn't work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;panic: The maximum number of retries exceeded. (50/50) [recovered]
        panic: The maximum number of retries exceeded. (50/50)

goroutine 7 [running]:
testing.tRunner.func1.2({0x800600, 0xc00028d640})
        /home/gregoire/.local/share/mise/installs/go/1.24.2/src/testing/testing.go:1734 +0x21c
testing.tRunner.func1()
        /home/gregoire/.local/share/mise/installs/go/1.24.2/src/testing/testing.go:1737 +0x35e
panic({0x800600?, 0xc00028d640?})
        /home/gregoire/.local/share/mise/installs/go/1.24.2/src/runtime/panic.go:792 +0x132
algolia-wiremock-testcontainers.indexRecord()
        /home/gregoire/Documents/blogging/wiremock/indexer.go:39 +0x166
algolia-wiremock-testcontainers.TestIndexRecord(0xc000198540)
        /home/gregoire/Documents/blogging/wiremock/indexer_test.go:40 +0x20a
testing.tRunner(0xc000198540, 0x8a6c10)
        /home/gregoire/.local/share/mise/installs/go/1.24.2/src/testing/testing.go:1792 +0xf4
created by testing.(*T).Run in goroutine 1
        /home/gregoire/.local/share/mise/installs/go/1.24.2/src/testing/testing.go:1851 +0x413
FAIL    algolia-wiremock-testcontainers 185.745s
FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have many applications on this instance, some of which are very busy, let us patch that real quick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Wait until indexing is done
_, err = client.WaitForTask(
    indexName,
    saveResp.TaskID,
    search.WithMaxRetries(100),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exactly the type of thing that unit tests will not catch.&lt;/p&gt;

&lt;p&gt;After that, the test passes (but it takes between several seconds or several minutes to run depending on how busy the instance on which the application is running is). Great! Now, let's add a proxy in the middle, and record all this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Wiremock in record mode 📼
&lt;/h3&gt;

&lt;p&gt;We are using Docker, so if we want to obtain the so-called "mapping files" Wiremock will create, we need to mount a volume on our Docker container, and mount it in the right location.&lt;/p&gt;

&lt;p&gt;Let us add 2 new dependencies to our project:&lt;/p&gt;

&lt;p&gt;We could interact with Wiremock by calling the REST API with the &lt;code&gt;net/http&lt;/code&gt; package, but as it turns out, there is a dedicated SDK for that, and it supports recording since &lt;a href="https://github.com/wiremock/go-wiremock/pull/33" rel="noopener noreferrer"&gt;this pull request I sent&lt;/a&gt;. Let's install it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go get github.com/wiremock/go-wiremock@v1.13.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will need a way to start and stop the Wiremock container, and for that&lt;br&gt;
as well, there is a library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;go get github.com/wiremock/wiremock-testcontainers-go@v1.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let us start the container, with a volume mounting &lt;code&gt;testdata&lt;/code&gt; in the current directory on &lt;code&gt;/home/wiremock/mappings&lt;/code&gt; in the container. This is where Wiremock will create json files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// indexer_test.go&lt;/span&gt;

&lt;span class="c"&gt;// for some reason wiremock doesn't like the testing context&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 

&lt;span class="n"&gt;absolutePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get current working directory: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Start the Wiremock container,  using the testcontainers library&lt;/span&gt;
&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunContainerAndStopOnCleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;testcontainers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHostConfigModifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;hostConfig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HostConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;hostConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Binds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;absolutePath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/testdata:/home/wiremock/mappings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"wiremock/wiremock:3.12.1"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create wiremock container: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// The endpoint changes every time, so we need to obtain it at runtime&lt;/span&gt;
&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get wiremock container endpoint: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to change how we instantiate the Algolia client, so that it calls Wiremock instead of Algolia:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClientWithConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchConfiguration&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;AppID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ApiKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Hosts&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatefulHost&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewStatefulHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create client: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that I have renamed the client to &lt;code&gt;algoliaClient&lt;/code&gt; to avoid confusion with the Algolia client and the Wiremock client.&lt;/p&gt;

&lt;p&gt;Let us also refactor our &lt;code&gt;indexRecord()&lt;/code&gt; function to take the client as an argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// indexer.go&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;indexRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;indexName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"test-index"&lt;/span&gt;

    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"objectID"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"object-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"test record"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;saveResp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SaveObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSaveObjectRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitForTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;saveResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TaskID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMaxRetries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's start the recording, and for that we need a client to call Wiremock's administration API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// indexer_test.go&lt;/span&gt;

&lt;span class="n"&gt;wiremockClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to reset wiremock: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c"&gt;// Call Wiremock's administration API to start recording&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"https://%s-dsn.algolia.net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to start recording: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Call Wiremock's administration API&lt;/span&gt;
        &lt;span class="c"&gt;// to stop recording and dump the mappings&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopRecording&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to stop recording: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c"&gt;// code that interacts with Algolia must be after&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's run our tests again, check our &lt;code&gt;testdata&lt;/code&gt; directory, and see what's new.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; testdata
&lt;span class="go"&gt;1_indexes_test-index-4c8f560d-010a-4db9-916c-f5c112481fc8.json
1_indexes_test-index-e766b4bf-6087-499b-975f-722c451f1d4a.json
1_indexes_test-index_query-e08439bf-fd60-4fac-8181-11b72f7a7a1c.json
1_indexes_test-index_settings-9de043d2-5803-40a0-b19b-49d0d2a17dde.json
1_indexes_test-index_task_228603771246-0004a7a6-3759-452c-8d0d-ea2a2f948ea1.json
1_indexes_test-index_task_228603771246-012f5d71-2645-496c-ba68-b85dcafbce51.json
1_indexes_test-index_task_228603771246-050f8624-f6f5-4815-96ed-03f319cdbda0.json
1_indexes_test-index_task_228603771246-05510684-a31f-41a6-96aa-d722a7527e87.json
1_indexes_test-index_task_228603771246-07651b21-23b0-45b4-9e13-386775c37432.json
1_indexes_test-index_task_228603771246-0bfcad3d-4d19-403f-9197-2fe6214beeb2.json
1_indexes_test-index_task_228603771246-0d450b99-1af0-4761-8d92-d7b92a68c702.json
1_indexes_test-index_task_228603771246-0dd66ddc-d7e2-4f70-8a3f-f5934ade7ac1.json
1_indexes_test-index_task_228603771246-1490e160-63e7-466d-9405-9437efd31c68.json
1_indexes_test-index_task_228603771246-1d25eb69-2a0f-448d-ae60-7075c380837d.json
1_indexes_test-index_task_228603771246-1f675d54-54bc-41cc-a3b4-f3dcc153cb0b.json
1_indexes_test-index_task_228603771246-1f829991-f442-45b6-9463-6ad064eb7576.json
1_indexes_test-index_task_228603771246-22837672-68b3-4684-a1f0-5bf19a8a3e8d.json
1_indexes_test-index_task_228603771246-25162e07-d196-4527-b476-173b7d62acf7.json
1_indexes_test-index_task_228603771246-26087497-e8ee-429a-a98e-a0100219c112.json
1_indexes_test-index_task_228603771246-264ce294-058a-482e-8d33-8be46740c7e9.json
1_indexes_test-index_task_228603771246-272cd045-d145-4b26-86b7-accad674a2db.json
1_indexes_test-index_task_228603771246-2ac3189e-9737-46df-a1b5-269e63a4d36a.json
1_indexes_test-index_task_228603771246-30a023df-b21b-43e7-b1a0-3f3c6ceec6d4.json
1_indexes_test-index_task_228603771246-315a9105-a169-4228-9d6a-7f20e9db3e4a.json
1_indexes_test-index_task_228603771246-3358c4a2-9c5d-4c7f-982f-1ca49c413b08.json
1_indexes_test-index_task_228603771246-34ef8259-1204-4026-9c1d-d43731a8d489.json
1_indexes_test-index_task_228603771246-37be5ee5-2f56-43a9-afe3-866859945d72.json
1_indexes_test-index_task_228603771246-3ba76a13-6052-4b9d-a7f2-61d3d364714d.json
1_indexes_test-index_task_228603771246-3be6df0a-3bcc-4104-81e0-28462875ed86.json
1_indexes_test-index_task_228603771246-3da6c29e-5603-4262-bfa8-37a06b021297.json
1_indexes_test-index_task_228603771246-3e321244-de3c-420e-978e-60b995839ca2.json
1_indexes_test-index_task_228603771246-41ed4f34-d05b-4e30-a9da-fd1b84d85aaa.json
1_indexes_test-index_task_228603771246-420e2d2c-2aa0-4afd-9cdd-5ac6ecc7bd20.json
1_indexes_test-index_task_228603771246-4220ae1a-447c-419f-86f9-6ee5bc40a121.json
1_indexes_test-index_task_228603771246-42b9612d-005a-4e5e-bb8c-e0e431790404.json
1_indexes_test-index_task_228603771246-450ab804-2980-478f-b1f2-c9cef5ba021c.json
1_indexes_test-index_task_228603771246-472b813d-f05a-4bca-bc14-c8f0b085388c.json
1_indexes_test-index_task_228603771246-4802a463-b49f-4ddf-83cd-49af7615c66a.json
1_indexes_test-index_task_228603771246-49ed408e-d531-4bc5-a711-2f1e4acc4ae2.json
1_indexes_test-index_task_228603771246-4a9c9104-f042-4f9a-a56b-dc4a9976bde7.json
1_indexes_test-index_task_228603771246-4ae60200-dbed-4c1b-878f-6dcf53cf4954.json
1_indexes_test-index_task_228603771246-4df81e83-7ce3-4ed1-9102-48c987a675de.json
1_indexes_test-index_task_228603771246-4e11ef64-ade6-40ca-b5c0-8a35684f73cd.json
1_indexes_test-index_task_228603771246-5383a9a9-7023-41f9-8092-09bbde387e7d.json
1_indexes_test-index_task_228603771246-55f8777f-8ff1-4fdb-ae35-86bd8d1641f6.json
1_indexes_test-index_task_228603771246-581cce11-3bbe-4492-b9b2-4bb4db47e50f.json
1_indexes_test-index_task_228603771246-58a94473-05f6-460d-b595-87d67a6389cc.json
1_indexes_test-index_task_228603771246-5aadb748-f27e-40b5-8166-33e61de59888.json
1_indexes_test-index_task_228603771246-5eabca6c-637d-4a3d-a883-9a0ac8d40449.json
1_indexes_test-index_task_228603771246-628fb430-b307-4257-8a23-becfcd8c1649.json
1_indexes_test-index_task_228603771246-660b5321-6c43-41e1-83d8-02f936a09bc9.json
1_indexes_test-index_task_228603771246-6b15a9eb-886f-43ff-af1e-2b82a8dacdf2.json
1_indexes_test-index_task_228603771246-6c610574-d0cf-4272-9260-cd5c42954b1c.json
1_indexes_test-index_task_228603771246-6e0d4b06-c3dc-47b8-858b-1d66ed65f708.json
1_indexes_test-index_task_228603771246-70303855-6eb1-465e-949b-40c85e6b5d2e.json
1_indexes_test-index_task_228603771246-712fc20e-8877-43e0-998b-3408c85a1645.json
1_indexes_test-index_task_228603771246-71ede81b-df18-4f39-97ea-757967a84599.json
1_indexes_test-index_task_228603771246-72c592d9-6d61-4a22-bf3e-3d2559b319a8.json
1_indexes_test-index_task_228603771246-72de2fd6-675c-42e6-85d0-21b3947b3fc1.json
1_indexes_test-index_task_228603771246-74ca6363-881f-49b8-8dac-efd6f5c8d449.json
1_indexes_test-index_task_228603771246-75f4709b-ef34-4eed-9d2e-002a3237a880.json
1_indexes_test-index_task_228603771246-773d2b22-6e45-4b0b-943b-b8e261c38d9b.json
1_indexes_test-index_task_228603771246-7accc591-6809-4483-9b39-578557dcabd3.json
1_indexes_test-index_task_228603771246-7bd2b559-9f16-4c57-92bb-0020763419b1.json
1_indexes_test-index_task_228603771246-837f6509-a8b8-4f0a-98fd-4f3b82349acc.json
1_indexes_test-index_task_228603771246-8875e9eb-07f3-4565-9e45-9db6b8c4a662.json
1_indexes_test-index_task_228603771246-8b464ab1-bef7-4975-972a-c472bfca9a90.json
1_indexes_test-index_task_228603771246-8c42ea24-0b42-4ed3-91a3-7cbce16828e0.json
1_indexes_test-index_task_228603771246-932fe93f-2121-4b1a-bedb-334a4518b986.json
1_indexes_test-index_task_228603771246-9697ecdf-4079-4af0-9df5-2d3f44f2b26c.json
1_indexes_test-index_task_228603771246-9bbbf58d-a4ea-47ba-af43-d7a455cc333a.json
1_indexes_test-index_task_228603771246-9d4fbd76-3f4c-4605-aa88-49d105bf718c.json
1_indexes_test-index_task_228603771246-9f126ad6-8a7a-4a37-80ef-7528c62f939d.json
1_indexes_test-index_task_228603771246-9f247f3d-9ba1-447f-a9d1-f61919a40d65.json
1_indexes_test-index_task_228603771246-a01ab516-b606-47b2-b4db-c1188fd9527a.json
1_indexes_test-index_task_228603771246-a057d6b6-00fe-40ea-aee8-bc0873bfe66d.json
1_indexes_test-index_task_228603771246-a460d1b7-6d5f-46b4-9196-a0a49c6c9e10.json
1_indexes_test-index_task_228603771246-a7c15221-1a8f-48b2-9c20-c90bdb6f0074.json
1_indexes_test-index_task_228603771246-a90bdd7d-7be0-428b-973f-1ba429c10f99.json
1_indexes_test-index_task_228603771246-b06c842b-102e-4ca9-99b2-38d838166480.json
1_indexes_test-index_task_228603771246-b2fbbe73-e3cb-49ee-a008-05a3eb66c93d.json
1_indexes_test-index_task_228603771246-b4a8ce95-12f3-4786-9dca-e989f86873ef.json
1_indexes_test-index_task_228603771246-baf4c6a3-ac78-44df-9e5d-8798e9dcf1ff.json
1_indexes_test-index_task_228603771246-bee44a4d-b090-4ebf-b2c7-3b85a0c92000.json
1_indexes_test-index_task_228603771246-c367963c-3aca-41dd-a570-c3135702c841.json
1_indexes_test-index_task_228603771246-c4f8d672-f2ed-4b37-ad51-04ceacf8f3e0.json
1_indexes_test-index_task_228603771246-cbf506c8-70c9-406a-afaa-7db6b7212345.json
1_indexes_test-index_task_228603771246-cc952870-85f4-4057-bb42-24940e3a9050.json
1_indexes_test-index_task_228603771246-d46e09e2-0634-40c6-b121-9c488000a698.json
1_indexes_test-index_task_228603771246-d85de2d1-2dbd-472f-b7d7-e288f833867c.json
1_indexes_test-index_task_228603771246-d998fa67-ef81-407d-9ced-4549b63be22e.json
1_indexes_test-index_task_228603771246-dbebd7ae-ef9a-41b4-9f67-8e8cb871522f.json
1_indexes_test-index_task_228603771246-dec87bcc-2c17-4f38-97dc-f00d3346a00b.json
1_indexes_test-index_task_228603771246-e1228862-9f69-4224-8f46-135b307edd5a.json
1_indexes_test-index_task_228603771246-e4ea7ed5-6f42-4eb7-a54c-f25609c54f3a.json
1_indexes_test-index_task_228603771246-e4fdbcf5-1941-44bd-87bb-8f7c9b0c4718.json
1_indexes_test-index_task_228603771246-ea16fd95-c2ca-474f-a4d2-e556c5bd158c.json
1_indexes_test-index_task_228603771246-f657c74c-0e47-4a6b-887d-e003ba7118c6.json
1_indexes_test-index_task_228603771246-fa7feea5-229a-49af-ac9d-f4754539eb55.json
1_indexes_test-index_task_228603771246-fb081872-590e-443a-8c8b-957ac58fa541.json
1_indexes_test-index_task_228603771246-ff880e8d-f1cf-43ad-9e21-dfe5dab7a3c7.json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… OK that is quite a lot of files. 😅 As mentioned earlier, a lot of them are about polling.&lt;/p&gt;

&lt;p&gt;Let's find the one that we should keep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; published testdata/&lt;span class="k"&gt;*&lt;/span&gt;task&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="go"&gt;
testdata/1_indexes_test-index_task_228603771246-0004a7a6-3759-452c-8d0d-ea2a2f948ea1.json:    "body" : "{\"status\":\"notPublished\",\"pendingTask\":false}",
testdata/1_indexes_test-index_task_228603771246-012f5d71-2645-496c-ba68-b85dcafbce51.json:    "body" : "{\"status\":\"notPublished\",\"pendingTask\":false}",
…
testdata/1_indexes_test-index_task_228603771246-3ba76a13-6052-4b9d-a7f2-61d3d364714d.json:    "body" : "{\"status\":\"published\",\"pendingTask\":false}",
…
testdata/1_indexes_test-index_task_228603771246-fb081872-590e-443a-8c8b-957ac58fa541.json:    "body" : "{\"status\":\"notPublished\",\"pendingTask\":false}",
testdata/1_indexes_test-index_task_228603771246-ff880e8d-f1cf-43ad-9e21-dfe5dab7a3c7.json:    "body" : "{\"status\":\"notPublished\",\"pendingTask\":false}",
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After removing the files with &lt;code&gt;notPublished&lt;/code&gt;, we are left with the following mapping files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; testdata
&lt;span class="go"&gt;1_indexes_test-index-4c8f560d-010a-4db9-916c-f5c112481fc8.json
1_indexes_test-index-e766b4bf-6087-499b-975f-722c451f1d4a.json
1_indexes_test-index_query-e08439bf-fd60-4fac-8181-11b72f7a7a1c.json
1_indexes_test-index_settings-9de043d2-5803-40a0-b19b-49d0d2a17dde.json
1_indexes_test-index_task_228603771246-3ba76a13-6052-4b9d-a7f2-61d3d364714d.json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Switching to playback mode 📺
&lt;/h3&gt;

&lt;p&gt;Now that we have our mapping files, we can switch to playback mode. Let us introduce a constant to turn recording and Algolia debugging on and off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// indexer_test.go&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

&lt;span class="c"&gt;// …&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"https://%s-dsn.algolia.net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to start recording: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopRecording&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to stop recording: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that I also moved the call to &lt;code&gt;debug.Enable()&lt;/code&gt; to the recording block, when replaying the tests, we do not really need to clutter the output with Algolia debug information.&lt;/p&gt;

&lt;p&gt;And now the test fails, with a rather clear error: apparently deleting the files was not enough, and we need to also edit the scenario name to outline that this is no longer the 43rd attempt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- FAIL: TestIndexRecord (1.87s)
panic: API error [404]
                                                       Request was not matched
                                                       =======================

        -----------------------------------------------------------------------------------------------------------------------
        | Closest stub                                             | Request                                                  |
        -----------------------------------------------------------------------------------------------------------------------
                                                                   |
        1_indexes_test-index_task_226434943725                     |
                                                                   |
        GET                                                        | GET
        /1/indexes/test-index/task/226434943725                    | /1/indexes/test-index/task/226434943725
                                                                   |
        [Scenario                                                  | [Scenario                                           &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; Scenario does not match
        'scenario-1-1-indexes-test-index-task-226434943725'        | 'scenario-1-1-indexes-test-index-task-226434943725'
        state:                                                     | state: Started]
        scenario-1-1-indexes-test-index-task-226434943725-43]      |
                                                                   |
        -----------------------------------------------------------------------------------------------------------------------
         [recovered]
        panic: API error [404]
                                                       Request was not matched
                                                       =======================

        -----------------------------------------------------------------------------------------------------------------------
        | Closest stub                                             | Request                                                  |
        -----------------------------------------------------------------------------------------------------------------------
                                                                   |
        1_indexes_test-index_task_226434943725                     |
                                                                   |
        GET                                                        | GET
        /1/indexes/test-index/task/226434943725                    | /1/indexes/test-index/task/226434943725
                                                                   |
        [Scenario                                                  | [Scenario                                           &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; Scenario does not match
        'scenario-1-1-indexes-test-index-task-226434943725'        | 'scenario-1-1-indexes-test-index-task-226434943725'
        state:                                                     | state: Started]
        scenario-1-1-indexes-test-index-task-226434943725-43]      |
                                                                   |
        -----------------------------------------------------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After dropping &lt;code&gt;"requiredScenarioState" : "scenario-1-1-indexes-test-index-task-226434943725-43",&lt;/code&gt; from the mapping file about polling, the test passes again, only this time, it passes in under 2 seconds.&lt;br&gt;
It is possible to mention which scenario a mapping belongs to, allowing to do things like "On the first 2 calls respond A, and on the 3rd return B". Based on that, it is possible to build a complex choreography of requests/responses, fulfilling all sorts of requirements.&lt;/p&gt;
&lt;h3&gt;
  
  
  Making it work in the CI
&lt;/h3&gt;

&lt;p&gt;After pushing the code, I got a bad surprise: the test fails in the CI, with the following message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tc-wiremock.go:73: create container: container create: Error response from daemon: Invalid bind mount config: mount source "/builds/product-discovery/ms.indexer/internal/import/brandsuggestion/testdata" is forbidden by the allow list [/home /tmp] - update the bind mounts configuration and restart the agent to enable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would seem that we cannot use a bind mount in the CI. Let us use our &lt;code&gt;record&lt;/code&gt; constant to make the container options conditional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// indexer_test.go&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Use a bind mount&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testcontainers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHostConfigModifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hostConfig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HostConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;hostConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Binds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;absolutePath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/testdata:/home/wiremock/mappings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Use a copy operation&lt;/span&gt;
    &lt;span class="n"&gt;mappingFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;absolutePath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/testdata"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to read testdata directory: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mappingFile&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;mappingFiles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMappingFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;mappingFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="s"&gt;"testdata/"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;mappingFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When recording, we mount the volume, which is not an issue because we are not in the CI.&lt;br&gt;
Otherwise, we use the &lt;code&gt;WithMappingFile&lt;/code&gt; function which relies on a copy operation.&lt;br&gt;
&lt;a href="https://pkg.go.dev/github.com/wiremock/wiremock-testcontainers-go#WithMappingFile" rel="noopener noreferrer"&gt;That function&lt;/a&gt; is provided by the&lt;br&gt;
&lt;code&gt;wiremock-testcontainers-go&lt;/code&gt; library, which abstracts away the low-level testcontainers API so that we can think in terms of mapping files rather than just JSON files.&lt;/p&gt;

&lt;p&gt;Not super satisfying, but it works.&lt;/p&gt;
&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The test is a bit long now, but some parts look generic and reusable. Let us extract them to helpers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// indexer_test.go&lt;/span&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;indexer&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/call"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/debug"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/search"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/algolia/algoliasearch-client-go/v4/algolia/transport"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/docker/docker/api/types/container"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/testcontainers/testcontainers-go"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/wiremock/go-wiremock"&lt;/span&gt;
    &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt; &lt;span class="s"&gt;"github.com/wiremock/wiremock-testcontainers-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;spinUpContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;absolutePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get current working directory: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;testcontainers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContainerCustomizer&lt;/span&gt;

    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"wiremock/wiremock:3.12.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;testcontainers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHostConfigModifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hostConfig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HostConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;hostConfig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Binds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;absolutePath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/testdata:/home/wiremock/mappings"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;mappingFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;absolutePath&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/testdata"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to read testdata directory: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mappingFile&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;mappingFiles&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMappingFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;mappingFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s"&gt;"testdata/"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;mappingFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;testcontainers_wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunContainerAndStopOnCleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create wiremock container: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get wiremock container endpoint: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;startRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;wiremockClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to reset wiremock: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"https://%s-dsn.algolia.net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to start recording: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;wiremockClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StopRecording&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to stop recording: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;newTestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;APIClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClientWithConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchConfiguration&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;AppID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ApiKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Hosts&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatefulHost&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewStatefulHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create client: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;algoliaClient&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestIndexRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;appID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_APP_ID"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ALGOLIA_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;indexName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"test-index"&lt;/span&gt;

    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;spinUpContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;startRecording&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;algoliaClient&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;newTestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSetSettingsRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"test-index"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEmptyIndexSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                &lt;span class="n"&gt;SetSearchableAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to set settings: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeleteIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiDeleteIndexRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to delete index: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;indexRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;searchResp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchSingleIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;algoliaClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewApiSearchSingleIndexRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
            &lt;span class="n"&gt;WithSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchParamsObjectAsSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEmptySearchParamsObject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
                        &lt;span class="n"&gt;SetQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No hits found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;firstHit&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;searchResp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c"&gt;// Assert that the first hit has the expected name&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;firstHit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdditionalProperties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"test record"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Expected name to be 'test record', got '%s'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;firstHit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdditionalProperties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now our test fits on a single screen 🙂&lt;br&gt;
I also added an extra assertion just to be sure we get the expected record, and that's OK, since it does not mean extra calls to Algolia.&lt;br&gt;
Now that we have paid the cost of writing that first step, writing more tests should be easier, and bring a lot of value to the project.&lt;/p&gt;

</description>
      <category>go</category>
      <category>wiremock</category>
      <category>algolia</category>
      <category>testcontainers</category>
    </item>
    <item>
      <title>Bisecting vendors</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Fri, 05 Jan 2024 12:10:11 +0000</pubDate>
      <link>https://dev.to/greg0ire/bisecting-vendors-12kd</link>
      <guid>https://dev.to/greg0ire/bisecting-vendors-12kd</guid>
      <description>&lt;p&gt;Hey there! If you're landing on this page, it's probably because somebody (most likely me), asked you to "bisect this regression down to a single commit". If that's not the case but you are still interested in helpfully reporting a regression, read on.&lt;/p&gt;

&lt;p&gt;I promise we'll see what this technical mumbo jumbo could possibly mean, but first, let us understand what the issue is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Regression! 💥
&lt;/h3&gt;

&lt;p&gt;You have an application you are working on, and you update your dependencies regularly, because you know hygiene matters. 🚿 The only way you will feel clean is when seeing &lt;code&gt;composer outdated&lt;/code&gt; outputs nothing.&lt;/p&gt;

&lt;p&gt;And then, one day, after an upgrade, one of your automated tests breaks. That's a regression, and since the only thing you did was upgrading your packages, it must be caused by a vendor. Since you're upgrading regularly, the list of packages you upgraded is quite small, and after reverting the upgrade, and upgrading the packages one by one, you narrow it down to a single package, that we will call &lt;code&gt;doctrine/orm&lt;/code&gt; to protect the innocent and the guilty.&lt;/p&gt;

&lt;p&gt;You know the right thing to do is to report the issue, and you proceed to do so, making sure to let people know that the issue didn't occur in "version 2.16.x" and then everything broke after upgrading to "version 2.17.x".&lt;/p&gt;

&lt;p&gt;And then… crickets… 🦗 Days elapse, and nobody answers. Why is that?&lt;/p&gt;

&lt;h3&gt;
  
  
  Open source development
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/doctrine/orm/compare/2.16.x...2.17.x" rel="noopener noreferrer"&gt;If you compare 2.17.x with 2.16.x&lt;/a&gt;, you can see quite a few contributions, over a long period of time, and by many different people. Some of them are part of the Doctrine core team, some others are returning contributors, and a few made their very first contribution to that repository. They all do that on their spare time, sometimes after a long day of paid work, take vacations, have a family to take care of. The core team is subscribed to issues, but it's unlikely that contributors are.&lt;/p&gt;

&lt;p&gt;At least you known what to do now: ping them all in a new message, of course! Hold on, hold on, I was just kidding, step away from that keyboard please. 🙏&lt;/p&gt;

&lt;p&gt;There is no need to ping anyone… it's possible that a maintainer already took a glance at the &lt;a href="https://github.com/doctrine/orm/releases" rel="noopener noreferrer"&gt;releases&lt;/a&gt; and tried to find a commit title that looks like it could have to do with your issue. And if they did not respond, then it's probably not that obvious.&lt;/p&gt;

&lt;p&gt;What would really help would be finding the right person to ping. This means you have to find the pull request, or even better, the commit that introduced the issue. The first thing to do is to update the issue and mention the exact version before and after the upgrade. It does count as narrowing things down.&lt;/p&gt;

&lt;p&gt;After checking, you find that you were on 2.16.3, and then upgraded to 2.17.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git bisect to the rescue
&lt;/h3&gt;

&lt;p&gt;If you don't know what &lt;code&gt;git bisect&lt;/code&gt; is, please hit pause on this blog post, and take a moment to go watch a part of this talk by Pauline Vos at Forum PHP 2021, and to practice &lt;code&gt;git bisect&lt;/code&gt; a bit yourself. This blog post can wait.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://www.youtube.com/watch?t=1269&amp;amp;v=x5Ib33eUUvo&amp;amp;feature=youtu.be" rel="noopener noreferrer"&gt;
      youtube.com
    &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;You're back? Great! Now… how can you apply that to this problem? Does Composer maybe have a magical subcommand to do this? Not quite it seems. There's a plan for something, but it won't help finding a single commit:&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/composer/composer/issues/11119" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Composer bisect command
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#11119&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/Seldaek" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F183678%3Fv%3D4" alt="Seldaek avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/Seldaek" rel="noopener noreferrer"&gt;Seldaek&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/composer/composer/issues/11119" rel="noopener noreferrer"&gt;&lt;time&gt;Oct 13, 2022&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Discussed in &lt;a href="https://github.com/composer/composer/discussions/11072" rel="noopener noreferrer"&gt;https://github.com/composer/composer/discussions/11072&lt;/a&gt;
&lt;/h3&gt;
&lt;div&gt;
&lt;p&gt;&lt;sup&gt;Originally posted by &lt;strong&gt;mad-briller&lt;/strong&gt; September 21, 2022&lt;/sup&gt;
This is just a random idea i've had while upgrading projects and having a hard time narrowing down issues, not sure if issues or discussions is the right place to put this.&lt;/p&gt;
&lt;p&gt;Even with all the will in the world to respect semver, packages inevitability break backwards compatibility in ways they did not forsee, everyone is human afterall.&lt;/p&gt;
&lt;p&gt;Chasing the version that introduced the issue can be quite hard currently, as you have to load up packagist and look at released versions and install them one by one to see if they are the issue.
This reduces the likelihood that a developer will report the issue to the package maintainers, and also makes the developer less likely to actually update the package, as its safer to stay on the "last working version" and put an explicit version number in &lt;code&gt;composer.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When a incorrect change is introduced in git, &lt;code&gt;git bisect&lt;/code&gt; makes it super easy to chase the commit that introduced the issue. It would be great if composer had a &lt;code&gt;composer bisect &amp;lt;package&amp;gt;&lt;/code&gt; that worked similarly.
This would make narrowing down which version introduced a bc break much easier.&lt;/p&gt;
&lt;p&gt;Thanks for your time.&lt;/p&gt;
&lt;/div&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/composer/composer/issues/11119" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Let's do it manually then. The first issue you are facing is that &lt;code&gt;doctrine/orm&lt;/code&gt; is probably not installed as a git repository, but was probably unpacked from an archive instead, so… there is no git history, this is just a snapshot. This means you cannot use &lt;code&gt;git bisect&lt;/code&gt; yet, you first have to reinstall &lt;code&gt;doctrine/orm&lt;/code&gt; as a git repository.&lt;/p&gt;

&lt;p&gt;To be fair, it's not going to be completely manual, Composer can still help us a lot here. There are ways you can make it prefer an install from source rather than from a tarball, and the simplest way I know to do this is to run &lt;code&gt;composer reinstall doctrine/orm --prefer-source&lt;/code&gt;. You should see some cloning happening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ composer reinstall doctrine/orm --prefer-source
  - Removing doctrine/orm (2.17.2)
  - Syncing doctrine/orm (2.17.2) into cache
  - Installing doctrine/orm (2.17.2): Cloning 393679a479 from cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the fun begins. Here is how to proceed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;vendor/doctrine/orm
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git switch &lt;span class="nt"&gt;--detach&lt;/span&gt; 2.16.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point you should maybe clear some caches, and check that things work. If they do work, you can start the bisection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git bisect start
&lt;span class="go"&gt;status: waiting for both good and bad commits
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git bisect good &lt;span class="c"&gt;# because it works 👍&lt;/span&gt;
&lt;span class="go"&gt;status: waiting for bad commit, 1 good commit known
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git switch &lt;span class="nt"&gt;--detach&lt;/span&gt; 2.17.0
&lt;span class="go"&gt;warning: you are switching branch while bisecting
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, you should clear the same caches, and check that the regression still happens.&lt;/p&gt;

&lt;p&gt;Does it still happen? Great. Mark the current commit as bad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git bisect bad &lt;span class="c"&gt;# because it broke 👎&lt;/span&gt;
&lt;span class="go"&gt;Bisecting: 33 revisions left to test after this (roughly 5 steps)
&lt;/span&gt;&lt;span class="gp"&gt;[some-hash] Merge pull request #&lt;/span&gt;42 from well-known-maintainer/tedious-maintenance-work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boom! Bisection started. Clear the caches… does it work? If yes, run &lt;code&gt;git bisect good&lt;/code&gt;, if not, run &lt;code&gt;git bisect bad&lt;/code&gt;, and repeat until you see something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;somehashffe340934df9 is the first bad commit
commit somehashffe340934df9
Author: Well Meaning Contributor &amp;lt;well-meaning@contribut.or&amp;gt;
Date:   Tue Feb 31 25:34:19 2026 +0200

    Cool change that hopefully breaks nothing (#12345)

 path/to/mission/critical/file.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congrats, now you have a name, a commit hash, and a pull request number (if you are lucky). Mention all three in your issue and you will get far better help. It should go without saying, and sadly often does not, but be nice when doing so.&lt;/p&gt;

&lt;p&gt;After that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The author is more likely to be aware of the issue and fix it.&lt;/li&gt;
&lt;li&gt;You and maintainers can examine the commit to see if they are able to spot
the issue, which just became way more likely to get fixed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you do not see a pull request number, you should try finding the commit on Github first (at &lt;code&gt;https://github.com/doctrine/orm/commit/somehashffe340934df9&lt;/code&gt;), and on that page, the pull request should appear right above the name of the author.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fluk4gtzfma8npscot9xa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fluk4gtzfma8npscot9xa.png" alt="A screenshot of Github's UI. The pull request number is visible" width="664" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bisecting a monorepository split
&lt;/h3&gt;

&lt;p&gt;Ok, but what if the issue is not with &lt;code&gt;doctrine/orm&lt;/code&gt;, but with &lt;code&gt;symfony/dependency-injection&lt;/code&gt;? The hash will be for &lt;code&gt;symfony/dependency-injection&lt;/code&gt;, and the corresponding hash for &lt;code&gt;symfony/symfony&lt;/code&gt; will be different. I often forget to mention this when asked to explain why I am not a huge fan of monorepositories, but this is definitely one of the reasons. You are not stuck though, it's just a bit more involving. What you can do in this kind of case is bypass Composer completely and symlink to a local clone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clones the monorepository somewhere on your disk (for instance in &lt;code&gt;/tmp/symfony&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;rm -fr vendor/symfony/dependency-injection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a symlink from &lt;code&gt;vendor/symfony/dependency-injection&lt;/code&gt; to &lt;code&gt;/tmp/symfony/src/Symfony/Component/DependencyInjection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Finally proceed with the bisection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you know of more elegant ways to do this, please let us know in the comments, and I will edit this post.&lt;/p&gt;

&lt;p&gt;Happy troubleshooting!&lt;/p&gt;

</description>
      <category>php</category>
      <category>composer</category>
      <category>git</category>
    </item>
    <item>
      <title>Feedback on sfCon 2022</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Thu, 15 Dec 2022 10:18:34 +0000</pubDate>
      <link>https://dev.to/manomano-tech-team/feedback-on-sfcon-2022-1pe5</link>
      <guid>https://dev.to/manomano-tech-team/feedback-on-sfcon-2022-1pe5</guid>
      <description>&lt;p&gt;This year, the &lt;a href="https://live.symfony.com/2022-paris-con/"&gt;SymfonyCon&lt;/a&gt; was even more magical than it usually is, as it took place in Disneyland Paris, thus contributing sparkles on top of the nice features announced during the keynote.&lt;/p&gt;

&lt;p&gt;Did you know that Symfony currently has no less than roughly 200 components? At the SymfonyCon, 2 new components were announced, that should make it into Symfony 6.3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Webhook: can be used to host a webhook, or call a webhook, typically with payment providers. Handles validity, security and signature checks.&lt;/li&gt;
&lt;li&gt;RemoteEvent: used to represent events produced outside Symfony&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is &lt;a href="https://github.com/symfony/symfony/pull/48542/files"&gt;the diff&lt;/a&gt; if you want to deep-dive head first! &lt;/p&gt;

&lt;p&gt;This is a big event, so there were no less than 4 tracks, which means I had to choose.&lt;/p&gt;

&lt;p&gt;First, I went to see a conference about Sylius and learnings the main maintainer has gathered over the past few years.&lt;br&gt;
He mentioned &lt;a href="https://dev.to/kevinjalbert/start-now-architecture-decision-records-580"&gt;ADRs&lt;/a&gt;, which we do use at ManoMano, and which would certainly be useful to also use in an open source setup (Doctrine maybe?).&lt;/p&gt;

&lt;p&gt;Next, I went to see Nicolas Grekas' talk about proxies: he presented different ways to implement a proxy system, and examples of each one that can be found in Symfony or Doctrine.&lt;br&gt;
Doctrine has an old implementation in &lt;code&gt;doctrine/common&lt;/code&gt; that we are trying to get rid off, and &lt;a href="https://github.com/doctrine/orm/pull/10187"&gt;Nicolas has proposed to replace those with Lazy Ghost proxies&lt;/a&gt;.&lt;br&gt;
I know, I know, funny name. 👻&lt;br&gt;
Lazy ghost proxies cannot be used with final classes, or internal classes, but do allow to use a fluent API, which makes them a good fit for &lt;code&gt;doctrine/orm&lt;/code&gt; entities.&lt;/p&gt;

&lt;p&gt;If you know me, you know I literally cannot shut up about Git, so for my next choice, I picked "advanced git magic" by Pauline Vos.&lt;br&gt;
The highlight was definitely this explanation about &lt;code&gt;git bisect&lt;/code&gt;:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5Be4wDaV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://social.taker.fr/system/media_attachments/files/109/358/863/606/123/532/original/1c18e0aa60623d30.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5Be4wDaV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://social.taker.fr/system/media_attachments/files/109/358/863/606/123/532/original/1c18e0aa60623d30.jpeg" alt="a slide with an egg-and-building-based metaphor" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After lunch, I heard there was a talk by one of my Doctrine fellows, Claudio Zizza a.k.a SenseException, which I first met in person at that conference, so of course, I &lt;em&gt;had&lt;/em&gt; to pick that one.&lt;br&gt;
Main takeways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always clear your entity manager at the end of a business class to avoid nasty side effects with &lt;code&gt;$entityManager-&amp;gt;clear()&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;(Postgre)SQL is very powerful, for instance you don't necessarily need to resort to &lt;a href="https://en.wikipedia.org/wiki/Nested_set_model"&gt;the nested set model&lt;/a&gt; to build a menu, you can use &lt;code&gt;WITH RECURSIVE&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A1If5rN2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://social.taker.fr/system/media_attachments/files/109/359/416/519/744/144/original/f0eed52a852e86b3.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A1If5rN2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://social.taker.fr/system/media_attachments/files/109/359/416/519/744/144/original/f0eed52a852e86b3.jpeg" alt="Claudio and big Doctrine logo" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was followed by a frightening talk form Sebastian Bergmann (himself!), who gave us a presentation of PHP's supply chain, and how each part of that chain has been exploited in the past, in PHP or for other ecosystems.&lt;br&gt;
Some of these vulnerabilities are so bad they have got their own website:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://meltdownattack.com/"&gt;https://meltdownattack.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://heartbleed.com/"&gt;https://heartbleed.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hertzbleed.com/"&gt;https://www.hertzbleed.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cuteboi.info/"&gt;https://cuteboi.info/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All the links above are safe to visit, I swear! That last one might require a bit more processing power though.&lt;/p&gt;

&lt;p&gt;Sebastian told us about the concept of Software bill of materials a.k.a. SBOM, as a way for you to know your dependencies and if they are vulnerable. You can try &lt;code&gt;./phpunit.phar --sbom&lt;/code&gt; to see it in action (only available when using the PHAR though).&lt;/p&gt;

&lt;p&gt;I had already seen Kevin Dunglas' talk about &lt;a href="https://frankenphp.dev"&gt;FrankenPHP&lt;/a&gt; at the "Forum PHP", which also took place at Disneyland the previous month (yes, really), but I decided to watch it again because of how packed with information it was.&lt;br&gt;
FrankenPHP provides a new PHP &lt;a href="https://en.wikipedia.org/wiki/Server_application_programming_interface"&gt;SAPI&lt;/a&gt;, that comes with a worker mode, that behaves much like Swoole or Roadrunner.&lt;br&gt;
Unlike php-fpm, it does not require a separate nginx container to run: it's built on top of &lt;a href="https://caddyserver.com/"&gt;the Caddy webserver&lt;/a&gt;, which is written in Go, and is also written in Go.&lt;br&gt;
Thanks to Kévin, Caddy is capable of forwarding 1xx HTTP responses it receives from its upstream, which unlocks using 103 Early Hints in PHP, a status code that aims at providing the browser with the urls of CSS files, JS files and other resources it might need before even sending the final response.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1557064869035347969-206" src="https://platform.twitter.com/embed/Tweet.html?id=1557064869035347969"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1557064869035347969-206');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1557064869035347969&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Next, I went on a conference about the impostor syndrome, and one of the main takeaway was that it's so common and equally spread that we should rather call it impostor phenomenon. It's estimated to be have been experienced by 70% of people (and yes, that includes me). If you're unfamiliar with it, you can tell yourself that it is close to atychiphobia or kakorraphiophobia. Hope this helps.&lt;/p&gt;

&lt;p&gt;That concluded the first day of conference. The 2 days were separated by a very nice evening with exclusive access to the Walt Disney Studios Park, which I must say is amazing, but I'll stop here because I don't want to make you jealous. But it was cool.&lt;/p&gt;

&lt;p&gt;On the second day, I started with a conference about climate change and IT. As a staff engineer, I'm currently working on upgrading many of our microservices from PHP 7 to PHP 8, and I was glad to learn that PHP 8.1 is estimated to be 30% faster/less electricity-consuming than PHP 7.4.&lt;br&gt;
There wasn't a similar figure to share about Symfony because it's harder to determine, but upgrading it is highly recommended as well.&lt;br&gt;
I learnt that the carbon footprint can be evaluated in several scope, and that for an IT company like ManoMano, most of it is in Scope 3, which is all about the cloud, Scope 1 and 2 being about office heating and electricity. To lower&lt;br&gt;
our impact, we should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upgrade our software&lt;/li&gt;
&lt;li&gt;Optimize our apps (with a profiler)&lt;/li&gt;
&lt;li&gt;Deploy to greener AWS regions: France, the Scandinavian peninsula (or &lt;a href="https://youtu.be/TsXMe8H6iyc?t=37"&gt;Fennoscandia&lt;/a&gt; for geography nerds), the Netherlands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was very interesting, so I continued with a talk on the same theme: it was about computing carbon emissions in the cloud.&lt;br&gt;
I learnt that "net zero" means the emissions of green house gases is equal to the removals, and that we are of course, very far from it, although it's a goal for 2050, that gets harder and harder to reach as time passes.&lt;br&gt;
There is a formula to compute the carbon footprint of a VM. For each capacity, such as the CPU, storage, network, you have a matching emission factor expressed in watts, and a Power Usage Effectiveness that depends on the cloud provider you are using, which is a constant &amp;gt; 1 that is better the closest it is to 1. You then multiply that by the carbon intensity, that depends on the region where you are deploying, and is a factor that allows you to transform watts in CO2 emissions.&lt;/p&gt;

&lt;p&gt;As the conference was getting closer to its end, I attended a talk about TDD.&lt;br&gt;
The speaker dove into detail about TDD and gave us pointers about methods to do it properly, such as "fake it till you make it", which means you should write dummy code until you have written enough tests that force you to ditch your&lt;br&gt;
initial, dummy implementation, or "triangulation", which consists in proving via tests that all parameters of a function are important, one by one.&lt;/p&gt;

&lt;p&gt;Next we had a talk about automated upgrades by Tomáš Votruba, the author of &lt;a href="https://github.com/rectorphp/rector"&gt;Rector&lt;/a&gt;. He told us about tooling you can have in your projects to make it easier to upgrade. For instance, you can use so-called PHPStan collectors to measure type coverage, and ensure it only ever increases. Having type information is very important for static analysis, which Rector relies upon to understand your code and migrate it. Likewise, using PHP configuration instead of YAML configuration can help avoiding mistakes thanks to Static Analysis (which exists for PHP but not for YAML). He showed us a demo of using Rector in the CI to continuously upgrade the code.&lt;/p&gt;

&lt;p&gt;The next talk I attended was by another fellow Doctrine maintainer: Alexander Turek. Alexander showed us strategies to modernize a legacy application with Symfony. It's possible for instance to introduce a global variable with the Symfony Kernel inside it to make Symfony services available inside the legacy application. That's called a micro rewrite because instead of migrating a full route to Symfony, you can migrate a service, deploy, rinse and repeat.&lt;br&gt;
Something we might want to apply to our legacy ManoMano application?&lt;/p&gt;

&lt;p&gt;The last talk I attended was by Mathieu Santostefano, a core team member. He presented the new &lt;code&gt;AccessTokenAuthenticator&lt;/code&gt; shipped with Symfony 6.2, explaining how it had been waiting for other lower-level Symfony components to be ready over the years. Part of the talk was about the contribution process, and I remember a hilarious slide showing 2 contributors opening 2 pull requests about the very same thing (implementing that authenticator).&lt;/p&gt;

&lt;p&gt;For the closing keynote, we had a light-hearted talk by Mickey Mouse himself presenting the evolution of Symfony over the past few months!&lt;/p&gt;

&lt;p&gt;If this feedback piqued your interest, you can find many of the talks here: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/SymfonyCon"&gt;
        SymfonyCon
      &lt;/a&gt; / &lt;a href="https://github.com/SymfonyCon/2022-talks"&gt;
        2022-talks
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
&lt;a href="https://live.symfony.com/2022-paris-con/" rel="nofollow"&gt;SymfonyCon - Paris 2022&lt;/a&gt; talks&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;All talks are in &lt;strong&gt;english&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You can send feedback and love to speakers on their twitter account&lt;/li&gt;
&lt;li&gt;See &lt;a href="https://twitter.com/search?q=(%23symfonycon%20OR%20%23symfonycon2022)%20until%3A2022-11-19%20since%3A2022-11-17%20-%23LuckyWebby&amp;amp;src=typed_query&amp;amp;f=top" rel="nofollow"&gt;all tweets&lt;/a&gt; during the event&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Keynote&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://speakerdeck.com/fabpot/symfonycon-2022-keynote-webhooks" rel="nofollow"&gt;Slides&lt;/a&gt;&lt;br&gt;
&lt;del&gt;Video&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;By &lt;a href="https://connect.symfony.com/profile/fabpot" rel="nofollow"&gt;Fabien Potencier&lt;/a&gt;&lt;br&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/SymfonyCon/2022-talksicon/github.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vVfKhQVi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/SymfonyCon/2022-talksicon/github.png" alt="github"&gt;&lt;/a&gt; &lt;a href="https://github.com/fabpot"&gt;@fabpot&lt;/a&gt;&lt;br&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/SymfonyCon/2022-talksicon/twitter.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XyFF_z0K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/SymfonyCon/2022-talksicon/twitter.png" alt="twitter"&gt;&lt;/a&gt; &lt;a href="https://twitter.com/fabpot" rel="nofollow"&gt;@fabpot&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
From monolith to decoupled…wait, why is that one getting bigger?!?&lt;/h2&gt;
&lt;dl&gt;
  &lt;dt&gt;Description&lt;/dt&gt;
  &lt;dd&gt;Monolithic apps are getting broken down left and right into dedicated services and teams, all under the banners of separating concerns, higher efficiency, and more. The strategy that is implemented will be crucial to ensure decoupling is a beacon of efficiency and not a migration nightmare
&lt;p&gt;In this talk, we’ll discuss how decoupling following the strangler fig approach will seem counter-intuitive, as your monolith continues to grow alongside your new decoupled architecture. But this approach, when done right, makes dismantling a monolith a process that is structured and safe, slow but agile, and without major service interruptions or massive interface changes that…&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/SymfonyCon/2022-talks"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Plotting the memory of a PHP process with Gnuplot 📈</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Fri, 26 Aug 2022 14:52:33 +0000</pubDate>
      <link>https://dev.to/manomano-tech-team/plotting-the-memory-of-a-php-process-with-gnuplot-3k02</link>
      <guid>https://dev.to/manomano-tech-team/plotting-the-memory-of-a-php-process-with-gnuplot-3k02</guid>
      <description>&lt;p&gt;Lately, I have been troubleshooting memory issues on a process my team owns.&lt;br&gt;
I started by watching this video about memory leaks from Benoît Jacquemont: &lt;iframe width="710" height="399" src="https://www.youtube.com/embed/wZjnj1PAJ78"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I learned a lot in the process, but also noticed the nice memory graphs in the video and figured it would be hard to troubleshoot anything if I didn't have them. &lt;br&gt;
When using php extensions such as &lt;a href="https://github.com/BitOne/php-meminfo" rel="noopener noreferrer"&gt;Benoît's&lt;/a&gt; or &lt;a href="https://github.com/arnaud-lb/php-memory-profiler" rel="noopener noreferrer"&gt;Arnaud Le Blanc's&lt;/a&gt; to take a snapshot of the memory, it's great to think about the most appropriate moment to take that snapshot in order to capture the memory leak you might be hunting.&lt;br&gt;
Sure, you can use Monolog's &lt;a href="https://seldaek.github.io/monolog/doc/02-handlers-formatters-processors.html" rel="noopener noreferrer"&gt;MemoryUsageProcessor&lt;/a&gt; to that end, but I thought it would be more useful to get something a bit more ✨visual✨.&lt;/p&gt;

&lt;p&gt;On our environments, we use Datadog, but on my development setup, I don't have that.&lt;/p&gt;

&lt;p&gt;There are several metrics you can track for troubleshooting a memory issue, some provided by the OS, and typically reported by Datadog, some others by PHP through &lt;code&gt;memory_get_usage()&lt;/code&gt; (which I don't currently have a way to monitor in production).&lt;/p&gt;
&lt;h1&gt;
  
  
  Measuring from PHP
&lt;/h1&gt;

&lt;p&gt;PHP provides several methods to understand what is happening memory-wise. First, you have &lt;code&gt;memory_get_usage()&lt;/code&gt;, which takes a boolean argument. Depending on that argument, the method returns the memory used by PHP, or the memory allocated to PHP. When freeing some memory, you will typically see the former decrease, while the latter stays stable.&lt;/p&gt;

&lt;p&gt;Then, you have &lt;code&gt;memory_get_peak_usage()&lt;/code&gt; which reports the highest value of used or allocated memory since the beginning of the script. That's useful because it can help the developer figure out that they are not calling &lt;code&gt;memory_get_usage()&lt;/code&gt; where memory usage is at its highest.&lt;/p&gt;
&lt;h2&gt;
  
  
  Producing metrics
&lt;/h2&gt;

&lt;p&gt;In the case of the script I was troubleshooting, I had a main loop that was frequently executed (but not at an even rate). That's still a good candidate for gathering metrics, as we will see.&lt;/p&gt;

&lt;p&gt;Here is what I put inside that loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'memory.tsv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="nb"&gt;memory_get_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="nb"&gt;memory_get_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="nb"&gt;memory_get_peak_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="nb"&gt;memory_get_peak_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="no"&gt;FILE_APPEND&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a TSV file that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1660831187  20  8.1252593994141 20.359375   19.608978271484
1660831187  20  8.1281814575195 20.359375   19.608978271484
1660831187  20  8.131103515625  20.359375   19.608978271484
1660831187  20  8.134033203125  20.359375   19.608978271484
1660831187  20  8.1369552612305 20.359375   19.608978271484
1660831187  20  8.1398773193359 20.359375   19.608978271484
1660831190  22  8.2328033447266 24.42578125 22.869613647461
1660831190  22  8.2357330322266 24.42578125 22.869613647461
1660831190  22  8.2386627197266 24.42578125 22.869613647461
1660831190  22  8.2415924072266 24.42578125 22.869613647461
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the first column, what stands out is that there are groups of lines that can be several seconds apart, so the production of metrics is really, really not paced regularly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plotting graphs
&lt;/h2&gt;

&lt;p&gt;Then, to create the graph, I turned to &lt;a href="http://www.gnuplot.info" rel="noopener noreferrer"&gt;gnuplot&lt;/a&gt;, which seems like a whole universe of its own as well as a very robust piece of software. I started by creating the following configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config.plt
set term png small size 800,600
set output "/tmp/memory_get_usage-graph.png"

set ylabel "memory in MB"

set yrange [0:*]

set xdata time # x is not just a random number
set timefmt "%s" # we use UNIX timestamps

plot "memory.tsv" using 1:2 with lines axes x1y1 title "memory_get_usage(true) in MB", \
     "memory.tsv" using 1:3 with lines axes x1y1 title "memory_get_usage(false) in MB", \
     "memory.tsv" using 1:4 with lines axes x1y1 title "memory_get_peak_usage(true) in MB", \
     "memory.tsv" using 1:5 with lines axes x1y1 title "memory_get_peak_usage(false) in MB"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, it's possible to let gnuplot know that the x axis represents time, which ensures you have a nicely formatted X axis.&lt;/p&gt;

&lt;p&gt;The graph is created by running &lt;code&gt;gnuplot config.plt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering the graph
&lt;/h2&gt;

&lt;p&gt;What would be handy would be a graph that refreshes over time.  For that, you will need 2 tiny programs: &lt;code&gt;watch&lt;/code&gt; and &lt;a href="https://feh.finalrewind.org" rel="noopener noreferrer"&gt;&lt;code&gt;feh&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;watch gnuplot config.plt&lt;/code&gt;, to ensure a png is created every 2 seconds (which is watch’s default).&lt;br&gt;
In parallel of that, you run &lt;code&gt;feh /tmp/memory_get_usage-graph.png&lt;/code&gt; to display the png file. What's great with &lt;code&gt;feh&lt;/code&gt; is that it refreshes automatically, so you don't need to do anything special to get your live graph. 🤯 &lt;code&gt;feh&lt;/code&gt; does very little, but does it well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0zsk1w3idr7mlusldgi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0zsk1w3idr7mlusldgi.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2 interesting things to note here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only the graph for &lt;code&gt;memory_get_usage(false)&lt;/code&gt; goes down, but it does go down, so there is no memory leak&lt;/li&gt;
&lt;li&gt;The defaults of gnuplot are a bit ugly, and I am no frontend developer, so it will stay ugly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Measuring from Linux
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Producing metrics
&lt;/h2&gt;

&lt;p&gt;Here, to produce the metrics, you can use &lt;code&gt;ps&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;ps &lt;span class="nt"&gt;--pid&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;pgrep &lt;span class="nt"&gt;-f&lt;/span&gt; some_string_that_identifies_your_process&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;,%mem&lt;span class="o"&gt;=&lt;/span&gt;,vsz&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /tmp/mem.log
    gnuplot config.plt
    &lt;span class="nb"&gt;sleep &lt;/span&gt;1
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you can of course use this for any process, not just PHP processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plotting graphs
&lt;/h2&gt;

&lt;p&gt;This time it's a bit more tricky, I am telling gnuplot to plot 2 metrics that&lt;br&gt;
have different units on the same graph.&lt;/p&gt;

&lt;p&gt;The left Y axis will have a scale for the first metric, and the right Y axis&lt;br&gt;
will have a scale for the second metric.&lt;/p&gt;

&lt;p&gt;I do not configure the X axis this time, since I'm producing metrics at a&lt;br&gt;
regular pace.&lt;/p&gt;

&lt;p&gt;This is all shamelessly stolen from &lt;a href="https://stackoverflow.com/questions/7998302/graphing-a-processs-memory-usage/12595110#12595110" rel="noopener noreferrer"&gt;Stack Overflow&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set term png small size 800,600
set output "/tmp/mem-graph.png"

set ylabel "VSZ"
set y2label "%MEM"

set ytics nomirror
set y2tics nomirror in

set yrange [0:*]
set y2range [0:*]

plot "/tmp/mem.log" using 3 with lines axes x1y1 title "VSZ", \
     "/tmp/mem.log" using 2 with lines axes x1y2 title "%MEM"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdat1u9p92kixajuomoaw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdat1u9p92kixajuomoaw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see that the figures are different than from inside PHP. I will not get into this because that is off topic, but when troubleshooting memory issues, it can also be important to compare both aspects.&lt;/p&gt;

&lt;h1&gt;
  
  
  Takeaway
&lt;/h1&gt;

&lt;p&gt;Those graphs helped me understand the differences between &lt;code&gt;memory_get_usage(true)&lt;/code&gt; and &lt;code&gt;memory_get_usage(false)&lt;/code&gt;, and gave me a better understanding of my application. In particular, I understood that the batch processing I was doing relied on batches of objects that were not all the same size, and that making sure they were all roughly the same size would help avoid situations where a series of big objects caused an out-of-memory error.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>memory</category>
      <category>gnuplot</category>
    </item>
    <item>
      <title>How to deprecate a type in php</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Sat, 29 Dec 2018 18:09:23 +0000</pubDate>
      <link>https://dev.to/greg0ire/how-to-deprecate-a-type-in-php-48cf</link>
      <guid>https://dev.to/greg0ire/how-to-deprecate-a-type-in-php-48cf</guid>
      <description>&lt;p&gt;So you have this class or interface you want to rename to something else, because you need to move that type to another package, or you have new coding standard rules that need to be applied to its name. One solution is to use inheritance and move all the code to the new type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="c1"&gt;// LegacyFoo.php&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * @deprecated please use ShinyNewFoo instead!
 */&lt;/span&gt;
&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;LegacyFoo&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ShinyNewFoo&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is great until your users write code that expects the old type, and gets the new type from your code instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt; &lt;span class="c1"&gt;// registered and invoked by your library&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;LegacyFoo&lt;/span&gt; &lt;span class="nv"&gt;$foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// crash&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But fear not, there is another approach, that consists in creating an alias for your type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="c1"&gt;// LegacyFoo.php&lt;/span&gt;

&lt;span class="nb"&gt;class_alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ShinyNewFoo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LegacyFoo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an alias from interface &lt;code&gt;LegacyFoo&lt;/code&gt;, to interface &lt;code&gt;ShinyNewFoo&lt;/code&gt;. You read that right, although it is &lt;code&gt;class_alias&lt;/code&gt; and I used the &lt;code&gt;class&lt;/code&gt; constant, both work for interfaces. But this is not enough, because nothing guarantees &lt;code&gt;LegacyFoo&lt;/code&gt; will be autoloaded at some point. Using that type in a parameter type declaration does not trigger autoload, because surely the type should be autoloaded when the object passed as parameter is instantiated. Well, this optimization does not work for type aliases, which means we have to manually trigger the autoload at the bottom of &lt;code&gt;ShinyNewFoo.php&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="c1"&gt;// ShinyNewFoo.php&lt;/span&gt;

&lt;span class="nb"&gt;class_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LegacyFoo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think this is over? Not so fast, there is more. Now that it all works, let us put this in production, and see it burst into flames! &lt;code&gt;class_alias&lt;/code&gt; calls are so rarely used that Composer does not look for them when generating its autoloader classmap. &lt;a href="https://getcomposer.org/doc/articles/autoloader-optimization.md"&gt;Autoload classmaps&lt;/a&gt; are a way to know which files to load without checking the filesystem first, which is faster. To trick Composer into detecting the legacy type, we can declare it as a piece of dead code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="c1"&gt;// LegacyFoo.php&lt;/span&gt;

&lt;span class="nb"&gt;class_alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ShinyNewFoo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LegacyFoo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LegacyFoo&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ShinyNewFoo&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will also fool most IDEs into providing auto-complete.&lt;/p&gt;

&lt;p&gt;And finally, what if we want to trigger a deprecation error when the legacy type is used? We cannot just do it since it will be autoloaded every time we use the new type. All we can do is implement a best-effort solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;class_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ShinyNewFoo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;trigger_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'LegacyFoo is deprecated!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kc"&gt;E_USER_DEPRECATED&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checks first if &lt;code&gt;ShinyNewFoo&lt;/code&gt; exists, &lt;em&gt;without triggering autoload&lt;/em&gt;. If it does not, then &lt;code&gt;LegacyFoo&lt;/code&gt; is referenced somewhere and we can safely trigger a deprecation.&lt;/p&gt;

&lt;p&gt;Done? The following seems to have been fixed with recent versions of php, you can ignore it if your library requires php &amp;gt;= 7.4.0.&lt;/p&gt;

&lt;p&gt;Otherwise… nope, not done, you sweet summer child! It goes deeper.&lt;/p&gt;

&lt;p&gt;This is an even rarer occurrence, but let us consider an interface that you expose, and that used the deprecated type in one of its signature and was switched to the new type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ShinyNewFoo&lt;/span&gt; &lt;span class="nv"&gt;$foo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What should happen to the implementation of your consumers?&lt;/p&gt;

&lt;p&gt;Let us also consider that class that does something similar, but that you did not mark as final… (or that could be &lt;code&gt;abstract&lt;/code&gt;, same issue).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Foobar&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;foobar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ShinyNewFoo&lt;/span&gt; &lt;span class="nv"&gt;$foo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What should happen to extending classes of your consumers?&lt;/p&gt;

&lt;p&gt;Well they shall crash and burn, of course! Since type declarations do not trigger autoload, the alias does not exist, so PHP cannot know both type declarations mean the same thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExtendingFoobar&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Foobar&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;LegacyFoo&lt;/span&gt; &lt;span class="nv"&gt;$foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 💥&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;foobar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;LegacyFoo&lt;/span&gt; &lt;span class="nv"&gt;$foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 💥&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unless… you guessed it, we need to add yet another call to &lt;code&gt;class_exists&lt;/code&gt; (or &lt;code&gt;interface_exists&lt;/code&gt;) call to trigger the autoload. In order not to get a deprecation, we will use that on the new type, and it will in turn silently load the old type and do the class alias.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ShinyNewFoo&lt;/span&gt; &lt;span class="nv"&gt;$foo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;class_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\ShinyNewFoo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To sum things up, every extensible interface, every interface that uses the old type in one of its signatures should make sure to autoload the new type.&lt;/p&gt;

&lt;p&gt;Done. Until next time. Wow that was hard, and I cannot say it feels very satisfying. I wish there were a native way in php to do all this.&lt;/p&gt;

&lt;p&gt;If you want to tinker with this problem yourself and try things out, here is a repo that might serve as a good starting point for you: &lt;a href="https://github.com/greg0ire/type_deprecation_experiment"&gt;https://github.com/greg0ire/type_deprecation_experiment&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>The case for multiline signatures</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Sat, 21 Oct 2017 18:18:29 +0000</pubDate>
      <link>https://dev.to/greg0ire/the-case-for-multiline-signatures-dl6</link>
      <guid>https://dev.to/greg0ire/the-case-for-multiline-signatures-dl6</guid>
      <description>&lt;p&gt;Hopefully, &lt;a href="https://dev.to/greg0ire/why-i-keep-pestering-everyone-about-long-lines"&gt;my last blog post&lt;/a&gt; convinced you to avoid long lines. The php community has a nice coding style guide called &lt;a href="http://www.php-fig.org/psr/psr-2/" rel="noopener noreferrer"&gt;PSR-2&lt;/a&gt; that says the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There MUST NOT be a hard limit on line length; the soft limit MUST be 120 characters; lines SHOULD be 80 characters or less.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Basically this means: do whatever you want, but your editor will warn if you go above 120 characters. The 80 characters limit is probably here to encourage people to add linebreaks even if their line is just 121 chars instead of saying "come on, it's only 1 character".&lt;/p&gt;

&lt;p&gt;Some projects take advantage of the limit being soft and disregard the soft limit for method signatures and will write this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$sourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$sourceValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$destId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$destValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$lazy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$weak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$sourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$sourceValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$destId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;$destValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$lazy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$weak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind that decision are many arguments that I'd like to discuss here. Just to be clear though: I'm not suggesting you should always use multiline signatures. In my opinion, they should only be used when the line length exceeds 120 characters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method signatures are not real code
&lt;/h2&gt;

&lt;p&gt;The main argument seems to be that method signatures are not "real code", because they declare things instead of doing things, and should thus not pollute the screen real estate. Except that they describe the interface of your method, and changes to them are very important, and should be reviewed carefully, because that is where most BC-breaks happen. Also, since php 7.0, function signatures are getting far more type hinting than before, which makes them longer, but also more useful, because they describe what type of variables you are dealing with. This is not always obvious unless you use &lt;a href="https://en.wikipedia.org/wiki/Hungarian_notation" rel="noopener noreferrer"&gt;hungarian notation&lt;/a&gt;, which you absolutely shouldn't.&lt;br&gt;
Having multiline signatures is not only useful for reviewers, but for people that edit that part of the code. If one argument maps to one line, navigating from argument to argument is suddenly way easier than whatever IDE shortcuts you would use otherwise.&lt;br&gt;
Still not convinced? Consider using &lt;a href="https://en.wikipedia.org/wiki/Code_folding" rel="noopener noreferrer"&gt;code folding&lt;/a&gt; instead of making the code uglier for everyone else, including and especially reviewers. If this suggestion sounds stupid, maybe it is and the signatures do matter after all?&lt;/p&gt;
&lt;h2&gt;
  
  
  Parameter types are already described in the phpdoc
&lt;/h2&gt;

&lt;p&gt;Then remove the phpdoc, unless you wrote something very interesting in it. Forcing people to describe each and every parameter in the phpdoc always results in the same paraphrasing comments in my experience, which has a very bad consequence: people stop reading the comments. Surely you can give the real estate you gave to the phpdoc to a piece of code? Most IDEs display comments in light gray nowadays, probably because of this, but I feel comments should not be mainly aimed at doc-extracting tools or annotation libraries. They should be helpful messages for your team, when your code fails to achieve enough clarity.&lt;/p&gt;
&lt;h2&gt;
  
  
  Long method signatures are an alarm signal about having too many arguments
&lt;/h2&gt;

&lt;p&gt;That one is really moot, because if you have a 100000-character long line, it will still be one line, whereas if your screen is filled with a signature, the alarm signal will only be louder, and tools like &lt;a href="https://phpmd.org/" rel="noopener noreferrer"&gt;phpmd&lt;/a&gt; will warn about your method having too many arguments.&lt;br&gt;
The first step towards realizing how wrong things are for &lt;a href="https://github.com/magento/magento2/blob/a5be15b42bee194f5030cb516002aab5244fe057/app/code/Magento/Catalog/Model/Product.php#L392" rel="noopener noreferrer"&gt;this method&lt;/a&gt; is definitely using a multiline signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Product constructor.
     * @param \Magento\Framework\Model\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory
     * @param AttributeValueFactory $customAttributeFactory
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $metadataService
     * @param Product\Url $url
     * @param Product\Link $productLink
     * @param Product\Configuration\Item\OptionFactory $itemOptionFactory
     * @param \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory
     * @param Product\OptionFactory $catalogProductOptionFactory
     * @param Product\Visibility $catalogProductVisibility
     * @param Product\Attribute\Source\Status $catalogProductStatus
     * @param Product\Media\Config $catalogProductMediaConfig
     * @param Product\Type $catalogProductType
     * @param \Magento\Framework\Module\Manager $moduleManager
     * @param \Magento\Catalog\Helper\Product $catalogProduct
     * @param ResourceModel\Product $resource
     * @param ResourceModel\Product\Collection $resourceCollection
     * @param \Magento\Framework\Data\CollectionFactory $collectionFactory
     * @param \Magento\Framework\Filesystem $filesystem
     * @param \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry
     * @param Indexer\Product\Flat\Processor $productFlatIndexerProcessor
     * @param Indexer\Product\Price\Processor $productPriceIndexerProcessor
     * @param Indexer\Product\Eav\Processor $productEavIndexerProcessor
     * @param CategoryRepositoryInterface $categoryRepository
     * @param Product\Image\CacheFactory $imageCacheFactory
     * @param ProductLink\CollectionProvider $entityCollectionProvider
     * @param Product\LinkTypeProvider $linkTypeProvider
     * @param \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory $productLinkFactory
     * @param \Magento\Catalog\Api\Data\ProductLinkExtensionFactory $productLinkExtensionFactory
     * @param EntryConverterPool $mediaGalleryEntryConverterPool
     * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
     * @param \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface $joinProcessor
     * @param array $data
     *
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Framework\Model\Context&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Framework\Registry&lt;/span&gt; &lt;span class="nv"&gt;$registry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Framework\Api\ExtensionAttributesFactory&lt;/span&gt; &lt;span class="nv"&gt;$extensionFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;AttributeValueFactory&lt;/span&gt; &lt;span class="nv"&gt;$customAttributeFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Store\Model\StoreManagerInterface&lt;/span&gt; &lt;span class="nv"&gt;$storeManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Api\ProductAttributeRepositoryInterface&lt;/span&gt; &lt;span class="nv"&gt;$metadataService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;Product\Url&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;Product\Link&lt;/span&gt; &lt;span class="nv"&gt;$productLink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\Product\Configuration\Item\OptionFactory&lt;/span&gt; &lt;span class="nv"&gt;$itemOptionFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory&lt;/span&gt; &lt;span class="nv"&gt;$stockItemFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\Product\OptionFactory&lt;/span&gt; &lt;span class="nv"&gt;$catalogProductOptionFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\Product\Visibility&lt;/span&gt; &lt;span class="nv"&gt;$catalogProductVisibility&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\Product\Attribute\Source\Status&lt;/span&gt; &lt;span class="nv"&gt;$catalogProductStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\Product\Media\Config&lt;/span&gt; &lt;span class="nv"&gt;$catalogProductMediaConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;Product\Type&lt;/span&gt; &lt;span class="nv"&gt;$catalogProductType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Framework\Module\Manager&lt;/span&gt; &lt;span class="nv"&gt;$moduleManager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Helper\Product&lt;/span&gt; &lt;span class="nv"&gt;$catalogProduct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\ResourceModel\Product&lt;/span&gt; &lt;span class="nv"&gt;$resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\ResourceModel\Product\Collection&lt;/span&gt; &lt;span class="nv"&gt;$resourceCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Framework\Data\CollectionFactory&lt;/span&gt; &lt;span class="nv"&gt;$collectionFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Framework\Filesystem&lt;/span&gt; &lt;span class="nv"&gt;$filesystem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Framework\Indexer\IndexerRegistry&lt;/span&gt; &lt;span class="nv"&gt;$indexerRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\Indexer\Product\Flat\Processor&lt;/span&gt; &lt;span class="nv"&gt;$productFlatIndexerProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\Indexer\Product\Price\Processor&lt;/span&gt; &lt;span class="nv"&gt;$productPriceIndexerProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\Indexer\Product\Eav\Processor&lt;/span&gt; &lt;span class="nv"&gt;$productEavIndexerProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;CategoryRepositoryInterface&lt;/span&gt; &lt;span class="nv"&gt;$categoryRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;Product\Image\CacheFactory&lt;/span&gt; &lt;span class="nv"&gt;$imageCacheFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\ProductLink\CollectionProvider&lt;/span&gt; &lt;span class="nv"&gt;$entityCollectionProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Model\Product\LinkTypeProvider&lt;/span&gt; &lt;span class="nv"&gt;$linkTypeProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Api\Data\ProductLinkInterfaceFactory&lt;/span&gt; &lt;span class="nv"&gt;$productLinkFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Catalog\Api\Data\ProductLinkExtensionFactory&lt;/span&gt; &lt;span class="nv"&gt;$productLinkExtensionFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;EntryConverterPool&lt;/span&gt; &lt;span class="nv"&gt;$mediaGalleryEntryConverterPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Framework\Api\DataObjectHelper&lt;/span&gt; &lt;span class="nv"&gt;$dataObjectHelper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface&lt;/span&gt; &lt;span class="nv"&gt;$joinProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides, with the php 7.0 type hinting, long signatures are going to happen way more often, and you may want to know if the cause is the number of arguments, the length of the type hints, the length of the variable names or a combination of all of these. If you address the real problem behind that, you might end up with more little methods with fewer arguments, which is usually easier to reason about. Don't sweep things under the rug, heed the warnings and design better code. &lt;a href="https://github.com/symfony/options-resolver" rel="noopener noreferrer"&gt;Symfony's Options Resolver Component&lt;/a&gt; might help with that if your arguments are actually interchangeable configuration options for something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework X, which my library uses, forbids multiline signatures
&lt;/h2&gt;

&lt;p&gt;You should strive to make your library framework-agnostic, and segregate it from framework-integrations layers. Style is part of that, use your own, don't let bad decisions spread like as many diseases, especially if you can't help but having many arguments with long names and long type hints because you love Java.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching to multiline signatures will be the source of many conflicts
&lt;/h2&gt;

&lt;p&gt;I'm a maintainer of some libraries that do not use multiline signatures. Guess where a big part of the conflicts happen? That's right, they happen on long lines, which nowadays practically only occur with long signatures. So maybe the conflicts are worth it in the long run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftp7l9hvl7zdorwnsclx3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftp7l9hvl7zdorwnsclx3.jpg" alt="long lines are a recipe for git conflicts" width="546" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, consider that switching to multiline signatures does not have to be done in one-shot: just allow switching to them, and enforce them on newly created methods when they are too long.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiline signatures look weird
&lt;/h2&gt;

&lt;p&gt;Remember switching to PSR-2? I remember, it felt horrible at first to have to write&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
if (defractulatorIsReady()) {
    defraculate();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
if (defractulatorIsReady())
{
    defraculate();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;like some of us did for years. But it was also nice to finally have a standard everyone could agree with, so I went with it anyway, and it felt horrible… for about half a day, and that was it. So if multiline signatures feel horrible, please try them a bit longer, instead of clinging to old habits, the language evolves, and so should you!&lt;/p&gt;

</description>
      <category>php</category>
      <category>devtips</category>
      <category>programming</category>
      <category>codingstyle</category>
    </item>
    <item>
      <title>Why I keep pestering everyone about long lines</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Sat, 09 Sep 2017 21:17:18 +0000</pubDate>
      <link>https://dev.to/greg0ire/why-i-keep-pestering-everyone-about-long-lines</link>
      <guid>https://dev.to/greg0ire/why-i-keep-pestering-everyone-about-long-lines</guid>
      <description>&lt;p&gt;When I decided to switch from Eclipse to vim, I remember being apprehensive of the clumsiness I would have to experience while facing vim's supposedly steep learning curve. I would have to lose lots of handy features, try to regain some of them plugin by plugin and survive with vim's at the time somewhat alien interface until I got comfortable with it.&lt;/p&gt;

&lt;p&gt;One thing that helped me regain a lot of the speed I formerly had was a very low-level feature: the ability to split windows. While on Eclipse, I had not&lt;br&gt;
even considered doing it because my window was filled with very "handy" panes that would show me a file tree, methods in the current file and so on. Once on vim, I found plugins to have those kind of panes back, but I did not seek to enable them by default because by the time I found those plugins I had realized a very important thing for my new workflow, something even more important than &lt;a href="https://github.com/junegunn/fzf.vim" rel="noopener noreferrer"&gt;the fact that I did not really need a menu&lt;/a&gt;: &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Being able to see several files at a time is way, way, way more important than having any of those panes.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;It is the same kind of feeling you get when you start working with an additional monitor.&lt;/p&gt;

&lt;p&gt;When I work on a piece of code, I typically have my vim window split in 4 or more vim "windows": the separation of concerns makes it so that you will often work with a service, a controller, corresponding tests, a template, some models and will need to quickly switch back and forth between them. When using Eclipse, I would often Ctrl+click keywords and navigate through the code, each click leaving me with one new tab I did not really care about, and then I would cleanup tabs I did not need. With vim, those tabs do not show up. They are still in the so-called buffer list, but that one is not visible by default.&lt;br&gt;
I found that a bit stupid at first, but then it clicked: just split your window several times, and display files you really care about. Instead of remembering their name and looking for them in a long list of tabs only a few of which you actually care about, just look at your screen and recognize the file by its contents. The switch is instant. And that is if you need to switch, because sometimes, all you need is to &lt;em&gt;read&lt;/em&gt; something in the file, not edit it. And if the file you want is not displayed, either split even more or consider replacing one of the windows you already have with the buffer that has the file you need. That file switch is a very low level action, and it is important to get right because you have to do it all the time.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0lkfya38rrykgj3qmos.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0lkfya38rrykgj3qmos.png" alt="worth it?" width="571" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's when I started paying a lot more attention to the length of the lines in my code. Nowadays, widescreens are the norm, and it seems to be an argument for people who disregard any limit in line length. But it is not about being able to edit files on an 80-chars wide terminal, like they often claim it is: it is about being able to see several files side by side.&lt;/p&gt;

&lt;p&gt;And even if you do not see the benefit of split windows, ask yourself why this blog post does not span all your screen, or &lt;a href="https://graphicdesign.stackexchange.com/questions/3553/why-do-newspapers-use-multiple-columns#3557" rel="noopener noreferrer"&gt;why newspapers use columns&lt;/a&gt;.&lt;br&gt;
Spoiler alert, it is, among other reasons, because it is far more efficient to scan that than &lt;a href="http://stopwritingramblingcommitmessages.com/" rel="noopener noreferrer"&gt;a long line of text&lt;/a&gt;. Convinced? Then &lt;a href="https://vimeo.com/74316116#t=5m0s" rel="noopener noreferrer"&gt;start writing your code for human beings&lt;/a&gt;, start using split windows and tell your fellow developer about that.&lt;/p&gt;

</description>
      <category>codingstyle</category>
      <category>programming</category>
      <category>devtips</category>
    </item>
    <item>
      <title>Why using Yoda conditions you should probably not be</title>
      <dc:creator>Grégoire Paris</dc:creator>
      <pubDate>Wed, 02 Aug 2017 21:47:31 +0000</pubDate>
      <link>https://dev.to/greg0ire/why-using-yoda-conditions-you-should-probably-not</link>
      <guid>https://dev.to/greg0ire/why-using-yoda-conditions-you-should-probably-not</guid>
      <description>&lt;p&gt;I had been using Yoda conditions for some time before I stumbled upon &lt;a href="https://stackoverflow.com/questions/2349378/new-programming-jargon-you-coined" rel="noopener noreferrer"&gt;this now deleted Stack Overflow Question&lt;/a&gt;, later &lt;a href="http://www.dodgycoder.net/2011/11/yoda-conditions-pokemon-exception.html" rel="noopener noreferrer"&gt;backed up in this blog post&lt;/a&gt;. I remember thinking "This could have saved me a lot of time!". Indeed, who can claim they have never written code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Luke Skywalker'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// this piece of code will always be executed&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So how do Yoda conditions work? Well it is basically a knee-jerk reaction you have to develop: whenever you write a condition, put the operand that cannot be assigned on the left. This should give you an error message if you make an assignment when you actually meant to make a comparison.&lt;/p&gt;

&lt;p&gt;In our case, this gives the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Luke Skywalker'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Parse error: syntax error, unexpected '='&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and finally, if you go all in and use strict comparison, this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Luke Skywalker'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In english, this translates to "If Luke Skywalker is the name", which should feel very familiar if you are from Dagobah or whichever planet Yoda actually came from, but is just cognitive load if you are used to english syntax, which you should if you are writing programs.&lt;/p&gt;

&lt;p&gt;I believe it has to become a knee-jerk reaction because if it does not, there is no point to it: if you can think of using Yoda conditions, then for sure you can think of checking if you really mean to write an assignment here or not. You have to use them without thinking.&lt;/p&gt;

&lt;p&gt;So let us say that you develop the knee-jerk reaction. You might end up with code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Luke Skywalker'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you see the issue here? Neither operands can be assigned, so there is no benefit, but the downside is still there.&lt;/p&gt;

&lt;p&gt;Soon enough, the disease spreads to inequalities, &lt;a href="https://github.com/symfony/symfony-docs/pull/5381" rel="noopener noreferrer"&gt;even in the documentation of my favorite framework&lt;/a&gt;, and you end up with needlessly convoluted code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$force&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a few things to ponder before picking Yoda. &lt;/p&gt;

&lt;p&gt;First, the only person who benefits from Yoda conditions, is you, the writer. It just makes life slightly harder for the people that read your code, which includes code reviewers, coworkers, or potential contributors. &lt;/p&gt;

&lt;p&gt;Also, if you test every code path, you should be able to spot the problem before it reaches the next step of your development pipeline. Tests can help with that, especially if you write them before writing the actual code, in a healthy red-green-refactor cycle&lt;/p&gt;

&lt;p&gt;Next, code review (including and especially &lt;a href="https://dev.to/sharpshark28/self-code-review-with-git-add-patch"&gt;self code-review&lt;/a&gt;) should catch the mistake if tests fail to do that.&lt;/p&gt;

&lt;p&gt;And finally, if you are not feeling a strong need for assignments in conditions anyway, you may as well use a static analyzer to enforce &lt;a href="https://github.com/slevomat/coding-standard/blob/master/README.md#slevomatcodingstandardcontrolstructuresassignmentincondition" rel="noopener noreferrer"&gt;a rule that forbids them&lt;/a&gt;. If you still would like to be able to use them you can also &lt;a href="https://github.com/slevomat/coding-standard/blob/master/README.md#slevomatcodingstandardcontrolstructuresyodacomparison-" rel="noopener noreferrer"&gt;prevent Yoda conditions and only them&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So go ahead, get rid of Yoda conditions today, and death to the Jedi.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9jfznsovbcohsgstsez.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9jfznsovbcohsgstsez.jpg" alt="see your enemy" width="619" height="768"&gt;&lt;/a&gt;&lt;/p&gt;
Image by Daryl Mandryk






</description>
      <category>codingstyle</category>
      <category>programming</category>
      <category>devtips</category>
    </item>
  </channel>
</rss>
