<?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>Forem: CodeCraft Diary</title>
    <description>The latest articles on Forem by CodeCraft Diary (@codecraft_diary_3d13677fb).</description>
    <link>https://forem.com/codecraft_diary_3d13677fb</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%2F3572568%2F9b328bfe-d229-4c84-8915-9af499c7bff0.png</url>
      <title>Forem: CodeCraft Diary</title>
      <link>https://forem.com/codecraft_diary_3d13677fb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/codecraft_diary_3d13677fb"/>
    <language>en</language>
    <item>
      <title>Laravel Testing Mistakes That Make Your Tests Useless</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 21 Apr 2026 13:00:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/laravel-testing-mistakes-that-make-your-tests-useless-136h</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/laravel-testing-mistakes-that-make-your-tests-useless-136h</guid>
      <description>&lt;p&gt;Testing in Laravel can feel straightforward at first. You write a few tests, run &lt;code&gt;php artisan test&lt;/code&gt;, see green output, and move on. But here’s the uncomfortable truth: &lt;strong&gt;many passing tests don’t actually protect your application&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your tests don’t catch real bugs, they’re not just useless—they give you a false sense of confidence.&lt;/p&gt;

&lt;p&gt;In this article, we’ll go through the most common Laravel testing mistakes that quietly break the value of your test suite, along with practical examples and better approaches.&lt;/p&gt;

&lt;p&gt;You can be also interested in testing databse logic: &lt;a href="https://codecraftdiary.com/2026/01/03/testing-database-logic-what-to-test-what-to-skip-and-why-it-matters/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2026/01/03/testing-database-logic-what-to-test-what-to-skip-and-why-it-matters/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Testing Implementation Instead of Behavior
&lt;/h2&gt;

&lt;p&gt;One of the biggest mistakes is writing tests that mirror your code instead of validating what your application actually does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bad example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_it_calls_service_method&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mockery&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserService&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="nv"&gt;$service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;shouldReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'createUser'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$controller&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&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 test only checks that a method was called. It doesn’t verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what was created&lt;/li&gt;
&lt;li&gt;whether the data is correct&lt;/li&gt;
&lt;li&gt;whether anything actually works&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Better approach
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_user_is_created&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$response&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'John Doe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&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;assertDatabaseHas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&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;Focus on &lt;strong&gt;observable behavior&lt;/strong&gt;, not internal calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Overusing Mocks
&lt;/h2&gt;

&lt;p&gt;Mocks are powerful—but overusing them leads to fragile and meaningless tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;When everything is mocked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you’re not testing real integration&lt;/li&gt;
&lt;li&gt;your tests pass even if the system is broken
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$response&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/weather'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells you nothing about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;response structure&lt;/li&gt;
&lt;li&gt;data correctness&lt;/li&gt;
&lt;li&gt;edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Better approach
&lt;/h3&gt;

&lt;p&gt;Mock only what you must (external services), and assert meaningful output:&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="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fake&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'temp'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$response&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/weather'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertJson&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'temperature'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;25&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;Rule of thumb:&lt;br&gt;
&lt;strong&gt;Mock boundaries, not your own logic.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Writing Tests That Always Pass
&lt;/h2&gt;

&lt;p&gt;Some tests are written in a way that they can’t fail—even if the code is broken.&lt;/p&gt;
&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_response_is_ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$response&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/users'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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 test will pass even if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the response is empty&lt;/li&gt;
&lt;li&gt;the wrong data is returned&lt;/li&gt;
&lt;li&gt;business logic is broken&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Better approach
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertJsonStructure&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'*'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&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;Or even better:&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertDatabaseCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask yourself:&lt;br&gt;
&lt;strong&gt;“What bug would this test catch?”&lt;/strong&gt;&lt;br&gt;
If the answer is “none”, rewrite it.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. Ignoring Edge Cases
&lt;/h2&gt;

&lt;p&gt;Most bugs don’t happen in the “happy path”. They happen at the edges.&lt;/p&gt;
&lt;h3&gt;
  
  
  Common mistake
&lt;/h3&gt;

&lt;p&gt;Only testing valid input:&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&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;h3&gt;
  
  
  Better approach
&lt;/h3&gt;

&lt;p&gt;Test invalid scenarios:&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/users'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'not-an-email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSessionHasErrors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing fields&lt;/li&gt;
&lt;li&gt;duplicate values&lt;/li&gt;
&lt;li&gt;unexpected input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good tests try to &lt;strong&gt;break your application&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Testing Too Much in Unit Tests
&lt;/h2&gt;

&lt;p&gt;Unit tests should be fast and focused. But many developers turn them into mini integration tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_order_creation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&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;assertDatabaseHas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&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 mixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;business logic&lt;/li&gt;
&lt;li&gt;database layer&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Better approach
&lt;/h3&gt;

&lt;p&gt;Split responsibilities:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit test (logic only):&lt;/strong&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_total_price_is_calculated_correctly&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderCalculator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&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;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$total&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;&lt;strong&gt;Feature test (full flow):&lt;/strong&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&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;assertDatabaseHas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'orders'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep your test layers clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Not Using Factories Properly
&lt;/h2&gt;

&lt;p&gt;Laravel factories are powerful, but many developers misuse them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;Hardcoding everything:&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="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'test@example.com'&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;h3&gt;
  
  
  Better approach
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even better:&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="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;state&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'email_verified_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;less boilerplate&lt;/li&gt;
&lt;li&gt;more flexible tests&lt;/li&gt;
&lt;li&gt;easier maintenance&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Not Cleaning Up Test Data
&lt;/h2&gt;

&lt;p&gt;Dirty test data can cause flaky tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;Tests depend on previous state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;Use:&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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Testing\RefreshDatabase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clean DB for each test&lt;/li&gt;
&lt;li&gt;consistent results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Flaky tests destroy trust in your test suite.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Writing Tests That Are Too Complex
&lt;/h2&gt;

&lt;p&gt;If your test is hard to read, it’s probably doing too much.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_everything&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 50 lines of setup&lt;/span&gt;
    &lt;span class="c1"&gt;// 10 assertions&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Better approach
&lt;/h3&gt;

&lt;p&gt;Break it down:&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_user_can_register&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;test_email_must_be_unique&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;test_password_is_required&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;Each test should answer one question.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Ignoring Performance
&lt;/h2&gt;

&lt;p&gt;Slow tests are often skipped—and skipped tests are useless tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;too many DB calls&lt;/li&gt;
&lt;li&gt;unnecessary setup&lt;/li&gt;
&lt;li&gt;heavy fixtures&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;use in-memory database (SQLite)&lt;/li&gt;
&lt;li&gt;avoid unnecessary seeding&lt;/li&gt;
&lt;li&gt;keep unit tests fast&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fast tests = tests you actually run.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Not Testing Real User Flows
&lt;/h2&gt;

&lt;p&gt;Testing isolated pieces is not enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;You test services and controllers separately, but never the full flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_user_can_register_and_login&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/register'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/login'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'password'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertRedirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/dashboard'&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 is what actually matters:&lt;br&gt;
&lt;strong&gt;Can the user complete the action?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Laravel makes testing easy—but writing &lt;em&gt;useful&lt;/em&gt; tests is a different skill.&lt;/p&gt;

&lt;p&gt;If your tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only check status codes&lt;/li&gt;
&lt;li&gt;mock everything&lt;/li&gt;
&lt;li&gt;mirror your implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…then they’re not protecting your application.&lt;/p&gt;

&lt;p&gt;Instead, focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;real behavior&lt;/li&gt;
&lt;li&gt;meaningful assertions&lt;/li&gt;
&lt;li&gt;edge cases&lt;/li&gt;
&lt;li&gt;realistic user flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A smaller set of high-quality tests is far more valuable than a large suite of weak ones.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Checklist
&lt;/h2&gt;

&lt;p&gt;Before committing a test, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this test fail if something important breaks?&lt;/li&gt;
&lt;li&gt;Am I testing behavior, not implementation?&lt;/li&gt;
&lt;li&gt;Would this catch a real bug?&lt;/li&gt;
&lt;li&gt;Is this test simple and readable?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is “no”, it’s time to improve it.&lt;/p&gt;




&lt;p&gt;Well-written tests are not just about coverage—they’re about confidence. And confidence comes from tests that actually matter.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>testing</category>
      <category>laravel</category>
      <category>php</category>
    </item>
    <item>
      <title>Fat Controller to Clean Architecture in Laravel (Step-by-Step Refactor)</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 14 Apr 2026 16:00:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/fat-controller-to-clean-architecture-in-laravel-step-by-step-refactor-3eii</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/fat-controller-to-clean-architecture-in-laravel-step-by-step-refactor-3eii</guid>
      <description>&lt;p&gt;Refactoring a fat controller in Laravel is one of the most impactful improvements you can make in a growing codebase. As projects evolve, controllers often become overloaded with validation, business logic, and side effects, making them difficult to maintain and test.&lt;/p&gt;

&lt;p&gt;A controller starts small. Clean. Readable.&lt;/p&gt;

&lt;p&gt;Then features get added.&lt;/p&gt;

&lt;p&gt;Deadlines hit.&lt;/p&gt;

&lt;p&gt;Logic piles up.&lt;/p&gt;

&lt;p&gt;And suddenly, you’re staring at a 500-line controller that handles validation, business logic, database writes, API calls, and maybe even a bit of formatting “just for now.”&lt;/p&gt;

&lt;p&gt;This is what we call a &lt;strong&gt;Fat Controller&lt;/strong&gt; — and it’s one of the most common maintainability problems in Laravel applications.&lt;/p&gt;

&lt;p&gt;In this article, we’ll take a real-world approach and refactor a fat controller into a cleaner, scalable structure using principles inspired by Clean Architecture.&lt;/p&gt;

&lt;p&gt;No theory overload. Just practical steps.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Problem: A Real Fat Controller Example
&lt;/h1&gt;

&lt;p&gt;Let’s start with something painfully familiar:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&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;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Validation&lt;/span&gt;
        &lt;span class="nv"&gt;$validated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|exists:users,id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'items'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|array'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// Business logic&lt;/span&gt;
        &lt;span class="nv"&gt;$total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$validated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&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="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Product not found'&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="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'quantity'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Not enough stock'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nv"&gt;$total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'quantity'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'quantity'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Save order&lt;/span&gt;
        &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$validated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'total'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="c1"&gt;// Save items&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$validated&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'order_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'product_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'quantity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'quantity'&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="c1"&gt;// External API call&lt;/span&gt;
        &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://example.com/webhook'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'order_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&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="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&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;h3&gt;
  
  
  What’s wrong here?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Controller handles &lt;strong&gt;too many responsibilities&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Business logic is &lt;strong&gt;not reusable&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Hard to &lt;strong&gt;test&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Tight coupling to Eloquent and external APIs&lt;/li&gt;
&lt;li&gt;Changes are risky&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Goal: What “Clean” Looks Like
&lt;/h1&gt;

&lt;p&gt;We want to move toward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thin controllers&lt;/li&gt;
&lt;li&gt;Isolated business logic&lt;/li&gt;
&lt;li&gt;Testable services&lt;/li&gt;
&lt;li&gt;Clear boundaries between layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re not going full academic Clean Architecture. We’re applying &lt;strong&gt;just enough structure to stay sane&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 1: Extract Business Logic into a Service
&lt;/h1&gt;

&lt;p&gt;First, move the core logic out of the controller.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderService&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;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Order&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&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="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Product not found'&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="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'quantity'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Not enough stock'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nv"&gt;$total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'quantity'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'quantity'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'total'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'items'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'order_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'product_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'quantity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'quantity'&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="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://example.com/webhook'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'order_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&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="nv"&gt;$order&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;Controller becomes:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&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;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CreateOrderService&lt;/span&gt; &lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$validated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'user_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|exists:users,id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'items'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'required|array'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$validated&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&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;h3&gt;
  
  
  Improvement:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Controller is now thin&lt;/li&gt;
&lt;li&gt;Logic is reusable&lt;/li&gt;
&lt;li&gt;Easier to test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But we’re not done yet.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 2: Introduce a Data Transfer Object (DTO)
&lt;/h1&gt;

&lt;p&gt;Passing raw arrays is fragile.&lt;/p&gt;

&lt;p&gt;Let’s fix that.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderData&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;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$items&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;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fromArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'items'&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;Update controller:&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="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderData&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fromArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$validated&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update service:&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CreateOrderData&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Order&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Improvement:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Stronger typing&lt;/li&gt;
&lt;li&gt;Safer refactoring&lt;/li&gt;
&lt;li&gt;Clear contract&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Step 3: Decouple External Dependencies
&lt;/h1&gt;

&lt;p&gt;Right now, your service is tightly coupled to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eloquent models&lt;/li&gt;
&lt;li&gt;HTTP client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s extract the webhook logic.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderWebhookService&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Order&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://example.com/webhook'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'order_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&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;Inject it:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderService&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;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;OrderWebhookService&lt;/span&gt; &lt;span class="nv"&gt;$webhook&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;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CreateOrderData&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Order&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// logic...&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="n"&gt;webhook&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$order&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;h3&gt;
  
  
  Improvement:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;External side effects are isolated&lt;/li&gt;
&lt;li&gt;Easier to mock in tests&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Step 4: Make It Testable
&lt;/h1&gt;

&lt;p&gt;Now you can test the service independently:&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_it_creates_order&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CreateOrderService&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="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;items&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="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'quantity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&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="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&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;assertNotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&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;Before refactoring, this would require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP mocking&lt;/li&gt;
&lt;li&gt;Controller testing&lt;/li&gt;
&lt;li&gt;Complex setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now it’s isolated.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 5: Optional — Introduce Repositories (Only If Needed)
&lt;/h1&gt;

&lt;p&gt;Don’t overengineer this.&lt;/p&gt;

&lt;p&gt;But if your app grows, you might extract:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductRepository&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?Product&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&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;Then inject it into the service.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to do this:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Multiple data sources&lt;/li&gt;
&lt;li&gt;Complex queries&lt;/li&gt;
&lt;li&gt;Domain logic reuse&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When NOT to:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Simple CRUD apps&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Before vs After
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Controller size&lt;/td&gt;
&lt;td&gt;Huge&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testability&lt;/td&gt;
&lt;td&gt;Hard&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reusability&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coupling&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Reduced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintainability&lt;/td&gt;
&lt;td&gt;Painful&lt;/td&gt;
&lt;td&gt;Scalable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Common Mistakes to Avoid
&lt;/h1&gt;

&lt;h3&gt;
  
  
  1. Moving everything blindly into services
&lt;/h3&gt;

&lt;p&gt;You’ll just create &lt;strong&gt;fat services instead of fat controllers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Keep services focused.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Overengineering with too many layers
&lt;/h3&gt;

&lt;p&gt;You don’t need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 interfaces&lt;/li&gt;
&lt;li&gt;5 abstractions&lt;/li&gt;
&lt;li&gt;enterprise architecture™&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start simple. Evolve when needed.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Ignoring boundaries
&lt;/h3&gt;

&lt;p&gt;Controllers = HTTP&lt;br&gt;
Services = business logic&lt;br&gt;
Models = persistence&lt;/p&gt;

&lt;p&gt;Mixing these again = back to chaos.&lt;/p&gt;




&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Fat controllers are a &lt;strong&gt;symptom&lt;/strong&gt;, not the root problem&lt;/li&gt;
&lt;li&gt;The real issue is &lt;strong&gt;mixed responsibilities&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Start with &lt;strong&gt;service extraction&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add DTOs for safety&lt;/li&gt;
&lt;li&gt;Isolate side effects (APIs, events)&lt;/li&gt;
&lt;li&gt;Only introduce more abstraction when justified&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Final Thought
&lt;/h1&gt;

&lt;p&gt;Clean Architecture in Laravel doesn’t mean rewriting your app into a textbook diagram.&lt;/p&gt;

&lt;p&gt;It means one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Making your code easier to change without fear.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And the fastest way to get there?&lt;/p&gt;

&lt;p&gt;Start killing your fat controllers — one method at a time.&lt;/p&gt;




&lt;p&gt;If this is something you’re dealing with right now, your next step is simple:&lt;/p&gt;

&lt;p&gt;Pick your worst controller and extract just one action into a service.&lt;/p&gt;

&lt;p&gt;That’s how clean architecture actually starts.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>laravel</category>
      <category>refactorit</category>
      <category>development</category>
    </item>
    <item>
      <title>Trunk-Based Development: Why Most Teams Think They Use It (But Don’t)</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 07 Apr 2026 13:20:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/trunk-based-development-why-most-teams-think-they-use-it-but-dont-o4g</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/trunk-based-development-why-most-teams-think-they-use-it-but-dont-o4g</guid>
      <description>&lt;p&gt;Trunk-Based Development sounds simple.&lt;/p&gt;

&lt;p&gt;No long-lived branches.&lt;br&gt;
Frequent merges.&lt;br&gt;
Small, incremental changes.&lt;/p&gt;

&lt;p&gt;Most teams will tell you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Yeah, we basically do trunk-based development.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In practice, they don’t.&lt;/p&gt;

&lt;p&gt;What they usually have is a hybrid that keeps the downsides of feature branches — while pretending to get the benefits of trunk-based development.&lt;/p&gt;

&lt;p&gt;I’ve seen this pattern in multiple backend teams. On paper, everything looks modern. In reality, delivery is still slow, pull requests are large, and integration is painful.&lt;/p&gt;

&lt;p&gt;So let’s break down what’s actually going on.&lt;/p&gt;


&lt;h1&gt;
  
  
  The Illusion of Trunk-Based Development
&lt;/h1&gt;

&lt;p&gt;Ask a team how they work, and you’ll often hear something like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“We merge to main frequently”&lt;/li&gt;
&lt;li&gt;“We don’t keep branches for too long”&lt;/li&gt;
&lt;li&gt;“We try to keep PRs small”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds good.&lt;/p&gt;

&lt;p&gt;But then you look closer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PRs are open for 3–5 days&lt;/li&gt;
&lt;li&gt;branches still contain multiple features&lt;/li&gt;
&lt;li&gt;merges are delayed because reviews are slow&lt;/li&gt;
&lt;li&gt;developers are afraid to merge unfinished work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not trunk-based development.&lt;/p&gt;

&lt;p&gt;This is just &lt;strong&gt;shorter feature branches&lt;/strong&gt;.&lt;/p&gt;


&lt;h1&gt;
  
  
  What Real Trunk-Based Development Actually Requires
&lt;/h1&gt;

&lt;p&gt;Trunk-based development is not about branches.&lt;/p&gt;

&lt;p&gt;It’s about &lt;strong&gt;integration frequency and safety&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At its core, it requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;merging to main at least daily (ideally multiple times per day)&lt;/li&gt;
&lt;li&gt;keeping changes small enough to review quickly&lt;/li&gt;
&lt;li&gt;having strong safety mechanisms in place&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without these, trunk-based development collapses.&lt;/p&gt;


&lt;h1&gt;
  
  
  Where Most Teams Break
&lt;/h1&gt;
&lt;h2&gt;
  
  
  1. Pull Requests Are Still Too Big
&lt;/h2&gt;

&lt;p&gt;This is the biggest issue.&lt;/p&gt;

&lt;p&gt;A developer starts a “small” feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adds endpoint&lt;/li&gt;
&lt;li&gt;updates service&lt;/li&gt;
&lt;li&gt;modifies database&lt;/li&gt;
&lt;li&gt;adds validation&lt;/li&gt;
&lt;li&gt;fixes something unrelated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the PR has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;15 files changed&lt;/li&gt;
&lt;li&gt;600+ lines&lt;/li&gt;
&lt;li&gt;multiple concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Review slows down. Feedback increases. Merge is delayed.&lt;/p&gt;

&lt;p&gt;At that point, it doesn’t matter what you call your workflow —&lt;br&gt;
you’re not doing trunk-based development.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Code Reviews Block Integration
&lt;/h2&gt;

&lt;p&gt;In theory:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We merge frequently”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In reality:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“We merge when the PR gets approved”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That delay is critical.&lt;/p&gt;

&lt;p&gt;If reviews take:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 day → integration is delayed&lt;/li&gt;
&lt;li&gt;2–3 days → conflicts increase&lt;/li&gt;
&lt;li&gt;5 days → context is lost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now developers start stacking changes on top of each other.&lt;/p&gt;

&lt;p&gt;And suddenly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;branches live longer&lt;/li&gt;
&lt;li&gt;PRs get bigger&lt;/li&gt;
&lt;li&gt;merges become risky&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  3. Teams Are Afraid to Merge Incomplete Work
&lt;/h2&gt;

&lt;p&gt;This is subtle but important.&lt;/p&gt;

&lt;p&gt;Developers often think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I can’t merge this yet — it’s not finished”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So they keep working on the branch.&lt;/p&gt;

&lt;p&gt;The problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the longer the branch lives&lt;/li&gt;
&lt;li&gt;the more it diverges&lt;/li&gt;
&lt;li&gt;the harder it is to merge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trunk-based development requires a different mindset:&lt;/p&gt;

&lt;p&gt;You merge incomplete work safely.&lt;/p&gt;


&lt;h1&gt;
  
  
  The Missing Piece: Feature Flags
&lt;/h1&gt;

&lt;p&gt;Most teams skip this.&lt;/p&gt;

&lt;p&gt;And without it, trunk-based development doesn’t work.&lt;/p&gt;
&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;You’re building a new payment flow.&lt;/p&gt;

&lt;p&gt;Without feature flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you need to finish everything before merging&lt;/li&gt;
&lt;li&gt;you keep a long-lived branch&lt;/li&gt;
&lt;li&gt;integration is delayed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With feature flags:&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Feature&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'new_payment_flow'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&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;newFlow&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;oldFlow&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;Now you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;merge partial work&lt;/li&gt;
&lt;li&gt;deploy continuously&lt;/li&gt;
&lt;li&gt;control exposure&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Real Example From Practice
&lt;/h1&gt;

&lt;p&gt;I worked with a team that claimed they were doing trunk-based development.&lt;/p&gt;

&lt;p&gt;Metrics looked like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;average PR size: ~500 lines&lt;/li&gt;
&lt;li&gt;review time: 2–3 days&lt;/li&gt;
&lt;li&gt;merges per developer: ~2 per week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After digging in, the issue was clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;developers grouped work “to be efficient”&lt;/li&gt;
&lt;li&gt;reviews were asynchronous and slow&lt;/li&gt;
&lt;li&gt;no feature flags&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  What changed
&lt;/h3&gt;

&lt;p&gt;We introduced 3 rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;PR must be mergeable within the same day&lt;/li&gt;
&lt;li&gt;no PR over ~300 lines (soft limit)&lt;/li&gt;
&lt;li&gt;feature flags for incomplete features&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Result after ~3 weeks:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;PR size dropped by ~40%&lt;/li&gt;
&lt;li&gt;review time dropped to hours&lt;/li&gt;
&lt;li&gt;merges increased to multiple per day&lt;/li&gt;
&lt;li&gt;production issues decreased&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not because developers got better.&lt;/p&gt;

&lt;p&gt;Because the system changed.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Hidden Constraint: CI/CD Speed
&lt;/h1&gt;

&lt;p&gt;Here’s something teams often ignore:&lt;/p&gt;

&lt;p&gt;You cannot do trunk-based development with slow pipelines.&lt;/p&gt;

&lt;p&gt;If your CI takes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20 minutes → friction&lt;/li&gt;
&lt;li&gt;40 minutes → developers wait&lt;/li&gt;
&lt;li&gt;60+ minutes → people stop merging frequently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So what happens?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;developers batch changes&lt;/li&gt;
&lt;li&gt;PRs get bigger&lt;/li&gt;
&lt;li&gt;integration slows down&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Rule of thumb (2026 reality):
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CI under 10 minutes → good&lt;/li&gt;
&lt;li&gt;under 5 minutes → ideal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anything above that:&lt;br&gt;
→ you’re actively fighting your workflow&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Matters More in 2026
&lt;/h1&gt;

&lt;p&gt;With AI-assisted coding, developers can generate code faster than ever.&lt;/p&gt;

&lt;p&gt;That creates a new problem:&lt;/p&gt;

&lt;p&gt;volume of changes increases&lt;br&gt;
but review capacity doesn’t&lt;/p&gt;

&lt;p&gt;If you don’t enforce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;small changes&lt;/li&gt;
&lt;li&gt;fast integration&lt;/li&gt;
&lt;li&gt;clear boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;your workflow collapses under its own weight.&lt;/p&gt;




&lt;h1&gt;
  
  
  How to Tell If You’re Actually Doing It
&lt;/h1&gt;

&lt;p&gt;Be honest and check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you merge to main multiple times per day?&lt;/li&gt;
&lt;li&gt;Are most PRs reviewed within hours, not days?&lt;/li&gt;
&lt;li&gt;Can you safely merge incomplete work?&lt;/li&gt;
&lt;li&gt;Are branches short-lived (hours, not days)?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If not:&lt;br&gt;
→ you’re not doing trunk-based development&lt;/p&gt;




&lt;h1&gt;
  
  
  Practical Rules You Can Apply Tomorrow
&lt;/h1&gt;

&lt;p&gt;If you want to move closer to real trunk-based development, start here:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Limit PR size
&lt;/h3&gt;

&lt;p&gt;Not as a guideline — as a rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Optimize for review speed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;fewer changes&lt;/li&gt;
&lt;li&gt;clearer intent&lt;/li&gt;
&lt;li&gt;less context switching&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Introduce feature flags
&lt;/h3&gt;

&lt;p&gt;Without them, you’re stuck.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Fix your CI/CD bottlenecks
&lt;/h3&gt;

&lt;p&gt;Speed is not a luxury — it’s a requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Stop batching work
&lt;/h3&gt;

&lt;p&gt;Batching feels efficient.&lt;br&gt;
It’s not.&lt;/p&gt;




&lt;h1&gt;
  
  
  Closing Thought
&lt;/h1&gt;

&lt;p&gt;Trunk-based development is not a branching strategy.&lt;/p&gt;

&lt;p&gt;It’s a discipline.&lt;/p&gt;

&lt;p&gt;Most teams don’t fail because they don’t understand it.&lt;br&gt;
They fail because they don’t change the constraints that make it possible.&lt;/p&gt;

&lt;p&gt;If your:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PRs are big&lt;/li&gt;
&lt;li&gt;reviews are slow&lt;/li&gt;
&lt;li&gt;CI is slow&lt;/li&gt;
&lt;li&gt;merges are risky&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then it doesn’t matter what you call your workflow.&lt;/p&gt;

&lt;p&gt;You’re still doing feature-branch development — just with better branding.&lt;/p&gt;

&lt;p&gt;And that’s exactly why your delivery still feels slower than it should.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>devops</category>
      <category>software</category>
      <category>development</category>
    </item>
    <item>
      <title>Laravel Testing: RefreshDatabase vs DatabaseTransactions (What Actually Matters)</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 31 Mar 2026 13:00:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/laravel-testing-refreshdatabase-vs-databasetransactions-what-actually-matters-4fje</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/laravel-testing-refreshdatabase-vs-databasetransactions-what-actually-matters-4fje</guid>
      <description>&lt;p&gt;Laravel RefreshDatabase vs DatabaseTransactions is one of the most common sources of confusion when writing tests in Laravel.&lt;/p&gt;

&lt;p&gt;Choosing the wrong approach can lead to flaky tests, hidden bugs, and unreliable results.&lt;/p&gt;

&lt;p&gt;When writing tests in Laravel, database state can quickly become a source of confusion.&lt;/p&gt;

&lt;p&gt;One test passes, another fails. Data seems to “leak” between tests. Records appear when they shouldn’t — or disappear when you expect them to exist.&lt;/p&gt;

&lt;p&gt;If you’ve experienced this, you’re not alone.&lt;/p&gt;

&lt;p&gt;In most cases, the issue comes down to how your tests handle the database. Laravel provides two primary approaches for this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RefreshDatabase
&lt;/li&gt;
&lt;li&gt;DatabaseTransactions
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first glance, they seem similar. In reality, they behave very differently — and choosing the wrong one can lead to subtle bugs or false confidence in your test suite.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding the Core Problem
&lt;/h2&gt;

&lt;p&gt;Tests must be isolated.&lt;/p&gt;

&lt;p&gt;Each test should run independently, without being affected by previous tests.&lt;/p&gt;

&lt;p&gt;If your database is not reset properly between tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data can persist unexpectedly
&lt;/li&gt;
&lt;li&gt;test results become unreliable
&lt;/li&gt;
&lt;li&gt;debugging becomes painful
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Laravel solves this problem using two strategies:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reset the database completely
&lt;/li&gt;
&lt;li&gt;Wrap each test in a transaction and roll it back
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Option 1: RefreshDatabase
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Testing\RefreshDatabase&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;UserTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;RefreshDatabase&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;&lt;strong&gt;How it works&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs migrations before tests&lt;/li&gt;
&lt;li&gt;Ensures a clean database state&lt;/li&gt;
&lt;li&gt;Uses transactions internally when possible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High reliability&lt;/li&gt;
&lt;li&gt;Works with queues, jobs, events&lt;/li&gt;
&lt;li&gt;Predictable behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slower&lt;/li&gt;
&lt;li&gt;Heavier setup&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 2: DatabaseTransactions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Testing\DatabaseTransactions&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;UserTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TestCase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;DatabaseTransactions&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;&lt;strong&gt;How it works&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starts a database transaction before each test&lt;/li&gt;
&lt;li&gt;Rolls it back after the test finishes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast&lt;/li&gt;
&lt;li&gt;Lightweight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;br&gt;
Does NOT work well with queues or async processes&lt;br&gt;
Can lead to hidden inconsistencies&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Each
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use RefreshDatabase when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testing queues or jobs&lt;/li&gt;
&lt;li&gt;working with multiple DB connections&lt;/li&gt;
&lt;li&gt;you need maximum reliability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use DatabaseTransactions when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testing simple DB logic&lt;/li&gt;
&lt;li&gt;performance is critical&lt;/li&gt;
&lt;li&gt;everything runs in one request lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Things Break in Real Projects
&lt;/h2&gt;

&lt;p&gt;This is where most developers run into problems.&lt;/p&gt;

&lt;p&gt;Especially when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;jobs don’t see database data&lt;/li&gt;
&lt;li&gt;tests pass locally but fail in CI&lt;/li&gt;
&lt;li&gt;retries cause unexpected behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;-&amp;gt;&lt;/strong&gt; I go deeper into real-world failures, debugging strategies, and edge cases here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;-&amp;gt;&lt;/strong&gt; &lt;a href="https://codecraftdiary.com/2026/03/28/laravel-refreshdatabase-vs-databasetransactions/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2026/03/28/laravel-refreshdatabase-vs-databasetransactions/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Use RefreshDatabase for reliability&lt;br&gt;
Use DatabaseTransactions for speed&lt;br&gt;
Avoid mixing both&lt;br&gt;
Be careful with queues and async behavior&lt;/p&gt;

&lt;p&gt;If you're working with Laravel testing, you might also find useful:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Queue testing pitfalls&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;-&amp;gt;&lt;/strong&gt; &lt;a href="https://codecraftdiary.com/2026/03/08/laravel-queue-testing-jobs-retries/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2026/03/08/laravel-queue-testing-jobs-retries/&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Feature testing in PHP&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;-&amp;gt;&lt;/strong&gt; &lt;a href="https://codecraftdiary.com/2025/10/30/feature-testing-in-php-ensuring-the-whole-system-works-together/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2025/10/30/feature-testing-in-php-ensuring-the-whole-system-works-together/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>laravel</category>
      <category>php</category>
      <category>testing</category>
    </item>
    <item>
      <title>AI-Driven Refactoring in PHP: When to Trust Copilot (and When to Take the Wheel)</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 24 Mar 2026 14:00:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/ai-driven-refactoring-in-php-when-to-trust-copilot-and-when-to-take-the-wheel-405b</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/ai-driven-refactoring-in-php-when-to-trust-copilot-and-when-to-take-the-wheel-405b</guid>
      <description>&lt;p&gt;In 2026, the biggest performance gains in PHP don’t come from micro-optimizations—they come from I/O strategy.&lt;/p&gt;

&lt;p&gt;Legacy PHP codebases (5–10 years old) are overwhelmingly synchronous and blocking. Refactoring them toward concurrency—using Fibers with an event loop like Revolt, Amp, or ReactPHP—can unlock massive gains.&lt;/p&gt;

&lt;p&gt;But this is also where AI tools like Copilot become dangerous.&lt;/p&gt;

&lt;p&gt;They can accelerate the refactor—or quietly corrupt your architecture.&lt;/p&gt;

&lt;p&gt;Previous article in this category: &lt;a href="https://codecraftdiary.com/2026/02/28/singleton-pattern-in-php/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2026/02/28/singleton-pattern-in-php/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The “Prompt–Refactor–Verify” Cycle
&lt;/h2&gt;

&lt;p&gt;When working with AI, the difference between success and subtle bugs is prompt precision.&lt;/p&gt;

&lt;p&gt;Bad prompt:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Make this async.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Good prompt:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Identify I/O-bound operations. Refactor using Fibers with an event loop (Revolt/Amp). Avoid shared mutable state. Ensure deterministic completion.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you paste a 500-line method without context, AI will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;miss hidden dependencies&lt;/li&gt;
&lt;li&gt;ignore state coupling&lt;/li&gt;
&lt;li&gt;introduce non-obvious bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI is a transformer, not a system thinker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example: Legacy Image Processor
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before (typical legacy flow)&lt;/strong&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="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$urls&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$image&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;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// blocking&lt;/span&gt;
    &lt;span class="nv"&gt;$resized&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;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// CPU&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;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$resized&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// blocking&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple. Predictable. Slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After (controlled concurrency with Amp)&lt;/strong&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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Amp\Future&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;Amp\async&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$urls&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$image&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;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// non-blocking if using async client&lt;/span&gt;
        &lt;span class="nv"&gt;$resized&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;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&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;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$resized&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="nv"&gt;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Future\awaitAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What changed?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I/O is now concurrent&lt;/li&gt;
&lt;li&gt;Execution is interleaved, not parallel threads&lt;/li&gt;
&lt;li&gt;The bottleneck shifts from waiting → throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The AI Trap: “It Works” ≠ “It’s Safe”
&lt;/h2&gt;

&lt;p&gt;AI often produces code that looks correct and even passes tests.&lt;/p&gt;

&lt;p&gt;But there are hidden risks.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Shared State Issues&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is dangerous:&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;processedUrls&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not because PHP is multithreaded—but because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;execution is interleaved&lt;/li&gt;
&lt;li&gt;order is no longer deterministic&lt;/li&gt;
&lt;li&gt;side effects accumulate unpredictably&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;return values → aggregate later&lt;/li&gt;
&lt;li&gt;avoid mutation inside async tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. The “AI Echo Chamber”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the most dangerous patterns:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI writes a bug → AI writes a test → test passes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Example:&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="c1"&gt;// Bug: returns null on failure&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$result&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI-generated test:&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is green. Everything is wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. “Happy Path” Bias&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI prefers:&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mf"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Throwable&lt;/span&gt; &lt;span class="nv"&gt;$e&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="kc"&gt;null&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 production systems, this is data loss.&lt;/p&gt;

&lt;p&gt;Senior-level expectation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explicit exceptions&lt;/li&gt;
&lt;li&gt;domain-level error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preventing Architecture Drift
&lt;/h2&gt;

&lt;p&gt;The biggest long-term risk isn’t bugs.&lt;/p&gt;

&lt;p&gt;It’s &lt;strong&gt;inconsistency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You refactor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Class A on Monday&lt;/li&gt;
&lt;li&gt;Class B on Tuesday&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By Friday:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;patterns diverge&lt;/li&gt;
&lt;li&gt;abstractions don’t align&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because AI only sees &lt;strong&gt;the current file&lt;/strong&gt;, not your system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The “Constraint Header” Strategy
&lt;/h2&gt;

&lt;p&gt;Before any refactor, define rules:&lt;/p&gt;

&lt;p&gt;Context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strict types only&lt;/li&gt;
&lt;li&gt;No direct DB calls (Repository pattern)&lt;/li&gt;
&lt;li&gt;Async only via Amp&lt;/li&gt;
&lt;li&gt;Immutable DTOs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Task:&lt;br&gt;
Refactor InvoiceGenerator&lt;/p&gt;

&lt;p&gt;This reduces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;random design decisions&lt;/li&gt;
&lt;li&gt;pattern drift&lt;/li&gt;
&lt;li&gt;“AI creativity” in critical code&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Killing the “God Constructor”
&lt;/h2&gt;

&lt;p&gt;Legacy PHP often looks 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="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="kt"&gt;Logger&lt;/span&gt; &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;Mailer&lt;/span&gt; &lt;span class="nv"&gt;$mailer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;Cache&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;PaymentGateway&lt;/span&gt; &lt;span class="nv"&gt;$gateway&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;AI’s default move:&lt;br&gt;
→ add another dependency&lt;/p&gt;

&lt;p&gt;Wrong direction.&lt;/p&gt;
&lt;h2&gt;
  
  
  Refactoring toward composition
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderProcessor&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;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;PaymentGateway&lt;/span&gt; &lt;span class="nv"&gt;$gateway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Notifier&lt;/span&gt; &lt;span class="nv"&gt;$notifier&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Order&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Pending&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="n"&gt;gateway&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Paid&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AlreadyPaidException&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\UnhandledMatchError&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&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="n"&gt;notifier&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Order &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; processed"&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;Key idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fewer responsibilities per class&lt;/li&gt;
&lt;li&gt;clearer boundaries&lt;/li&gt;
&lt;li&gt;easier testing&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Hallucinated APIs: The “Ghost Methods” Problem
&lt;/h2&gt;

&lt;p&gt;AI sometimes invents functions that sound correct:&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="nf"&gt;array_first_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$array&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ❌ does not exist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you don’t recognize it, verify it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Which PHP version introduced this?”&lt;/li&gt;
&lt;li&gt;“Show official docs”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No answer → hallucination.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Cost: AI-Grown Code
&lt;/h2&gt;

&lt;p&gt;If AI writes most of your refactoring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code becomes harder to reason about&lt;/li&gt;
&lt;li&gt;intent disappears&lt;/li&gt;
&lt;li&gt;onboarding slows down&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Countermeasure:&lt;/p&gt;

&lt;p&gt;Ask AI:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Explain why this design was chosen over a simpler alternative.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the answer is vague → the design is weak.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing AI Code Like a Senior
&lt;/h2&gt;

&lt;p&gt;Static analysis won’t save you.&lt;/p&gt;

&lt;p&gt;Tools like PHPStan check correctness—not intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually review
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Semantic correctness&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Did we simplify—or just move complexity?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Concurrency safety&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hidden state mutation&lt;/li&gt;
&lt;li&gt;ordering assumptions&lt;/li&gt;
&lt;li&gt;error propagation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Edge cases&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ask AI explicitly:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Simulate a timeout during async execution. What breaks?”&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2026 Refactoring Checklist
&lt;/h2&gt;

&lt;p&gt;Before merging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type safety → strict, explicit&lt;/li&gt;
&lt;li&gt;No hidden shared state&lt;/li&gt;
&lt;li&gt;Async is intentional (not accidental)&lt;/li&gt;
&lt;li&gt;Dependencies are minimal and meaningful&lt;/li&gt;
&lt;li&gt;Errors are explicit (no silent nulls)&lt;/li&gt;
&lt;li&gt;You can explain every change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If not:&lt;/p&gt;

&lt;p&gt;→ don’t merge it&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;AI is an accelerator—but also a multiplier of mistakes.&lt;/p&gt;

&lt;p&gt;It tends to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add complexity&lt;/li&gt;
&lt;li&gt;over-engineer&lt;/li&gt;
&lt;li&gt;hide intent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your role is no longer just writing code.&lt;/p&gt;

&lt;p&gt;It’s &lt;strong&gt;curating it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The real senior skill in 2026 is knowing when to say:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“This is too clever. Make it simple.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because great refactoring isn’t about adding concurrency.&lt;/p&gt;

&lt;p&gt;It’s about &lt;strong&gt;removing everything that doesn’t need to be there&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>php</category>
      <category>refactorit</category>
    </item>
    <item>
      <title>Small Pull Requests: Why They Move Development Teams Faster</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 17 Mar 2026 14:00:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/small-pull-requests-why-they-move-development-teams-faster-20kh</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/small-pull-requests-why-they-move-development-teams-faster-20kh</guid>
      <description>&lt;p&gt;In many backend teams, pull requests slowly grow into something nobody wants to review.&lt;/p&gt;

&lt;p&gt;A developer starts working on a feature.&lt;br&gt;
At first, it’s just a small change — maybe a new endpoint or a service update.&lt;/p&gt;

&lt;p&gt;Soon another improvement gets added.&lt;/p&gt;

&lt;p&gt;A small refactor follows.&lt;/p&gt;

&lt;p&gt;Finally, a fix for something unrelated appears.&lt;/p&gt;

&lt;p&gt;Two days later, the pull request contains &lt;strong&gt;900 lines of changes across 14 files.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At that moment, something predictable happens.&lt;/p&gt;

&lt;p&gt;The review slows down.&lt;/p&gt;

&lt;p&gt;Not because the reviewers are lazy — but because &lt;strong&gt;large pull requests create cognitive overload.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And once reviews slow down, the entire development workflow begins to lose momentum.&lt;/p&gt;

&lt;p&gt;Previous article in this category: &lt;a href="https://codecraftdiary.com/2026/02/21/why-just-one-more-quick-fix/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2026/02/21/why-just-one-more-quick-fix/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Psychological Problem of Large Pull Requests
&lt;/h2&gt;

&lt;p&gt;Large pull requests create a subtle psychological effect.&lt;/p&gt;

&lt;p&gt;When a reviewer opens a PR and sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+824 −217 changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;their brain immediately categorizes it as &lt;strong&gt;expensive work.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result is predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The review gets postponed.&lt;/li&gt;
&lt;li&gt;The reviewer waits for “more time”.&lt;/li&gt;
&lt;li&gt;The PR sits for hours or days.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually, when someone finally reviews it, they cannot realistically analyze everything in depth.&lt;/p&gt;

&lt;p&gt;So one of two things happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The reviewer skims the code and approves it quickly.&lt;/li&gt;
&lt;li&gt;The reviewer leaves many comments that trigger long discussion threads.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Neither outcome improves delivery speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small Pull Requests Change the Dynamic
&lt;/h2&gt;

&lt;p&gt;Now compare that to a PR 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;+42 −8 changes

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feels manageable.&lt;/p&gt;

&lt;p&gt;A reviewer can realistically read the entire diff in a few minutes.&lt;/p&gt;

&lt;p&gt;This leads to several important effects:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Faster Reviews
&lt;/h2&gt;

&lt;p&gt;Small PRs get reviewed faster simply because they feel easier.&lt;/p&gt;

&lt;p&gt;Many teams see review times drop from &lt;strong&gt;days to hours&lt;/strong&gt; after adopting smaller pull requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Better Feedback
&lt;/h2&gt;

&lt;p&gt;When the code is smaller, reviewers can focus on &lt;strong&gt;design decisions instead of scanning files.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of writing comments like:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“This file changed a lot, can you explain the logic?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;they can ask more meaningful questions:&lt;/p&gt;

&lt;p&gt;“&lt;em&gt;Should this logic live in the service layer instead?”&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Less Risk
&lt;/h2&gt;

&lt;p&gt;Large pull requests often hide bugs.&lt;/p&gt;

&lt;p&gt;For example, reviewers may miss subtle mistakes when hundreds of lines change at once.&lt;/p&gt;

&lt;p&gt;Small PRs isolate changes.&lt;/p&gt;

&lt;p&gt;If a bug appears, it’s easier to trace it back to a specific change.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Example
&lt;/h2&gt;

&lt;p&gt;Consider a backend developer implementing a new feature:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send notification emails when an order is shipped.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A typical large PR might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;database migration&lt;/li&gt;
&lt;li&gt;new notification service&lt;/li&gt;
&lt;li&gt;email template&lt;/li&gt;
&lt;li&gt;queue job&lt;/li&gt;
&lt;li&gt;controller changes&lt;/li&gt;
&lt;li&gt;unit tests&lt;/li&gt;
&lt;li&gt;refactoring an old helper&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All combined into &lt;strong&gt;one big pull request.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead, this can be split into several smaller ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR 1 – Database Migration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add shipped_at column to orders table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple schema change.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR 2 – Notification Service
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create OrderShipmentNotifier service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pure backend logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR 3 – Queue Job
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add SendShipmentEmail job
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Background processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR 4 – Controller Integration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trigger notification when order is shipped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integration step.&lt;/p&gt;

&lt;p&gt;Each PR becomes &lt;strong&gt;reviewable within minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The entire feature moves through the pipeline &lt;strong&gt;much faster&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Benefit: Continuous Integration
&lt;/h2&gt;

&lt;p&gt;Small pull requests also improve CI pipelines.&lt;/p&gt;

&lt;p&gt;Large PRs often cause several problems:&lt;/p&gt;

&lt;p&gt;When a small PR fails CI, the cause is usually obvious.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PR title: Add OrderShipmentNotifier service
CI failure: NotificationServiceTest fails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is straightforward.&lt;/p&gt;

&lt;p&gt;Compare this to a massive PR with dozens of changes — the failure may come from anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developers Resist Small Pull Requests
&lt;/h2&gt;

&lt;p&gt;Despite the benefits, many developers hesitate to split their work.&lt;/p&gt;

&lt;p&gt;Common reasons include:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;em&gt;“The feature isn’t finished yet.”&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;This is the most common argument.&lt;/p&gt;

&lt;p&gt;But many PRs &lt;strong&gt;do not need to represent a finished feature.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They only need to represent a safe incremental step.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;You can merge a service class before it’s used anywhere.&lt;/p&gt;

&lt;p&gt;That’s perfectly valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;em&gt;“It creates too many PRs.”&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;This concern sounds logical but rarely holds in practice.&lt;/p&gt;

&lt;p&gt;Ten small PRs usually move through the system faster than one large one.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because multiple reviewers can process them quickly instead of blocking on one huge change.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;em&gt;“It’s extra work.”&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;At first, splitting work into smaller PRs requires discipline.&lt;/p&gt;

&lt;p&gt;But after a few weeks, it becomes natural.&lt;/p&gt;

&lt;p&gt;Developers start thinking in &lt;strong&gt;incremental changes&lt;/strong&gt; rather than large feature drops.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Rule That Works
&lt;/h2&gt;

&lt;p&gt;Some teams use a simple heuristic:&lt;/p&gt;

&lt;p&gt;If a pull request takes more than &lt;strong&gt;10 minutes to review&lt;/strong&gt;, it is probably too large.&lt;/p&gt;

&lt;p&gt;This isn’t a strict rule, but it works surprisingly well.&lt;/p&gt;

&lt;p&gt;Another guideline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;200–300 lines of diff&lt;/strong&gt; should usually be the upper limit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Beyond that, review quality drops quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Long-Term Impact on Workflow
&lt;/h2&gt;

&lt;p&gt;When teams consistently use small pull requests, several improvements appear over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline becomes predictable
&lt;/h2&gt;

&lt;p&gt;PRs move steadily through the system instead of forming large queues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developers ship code more often
&lt;/h2&gt;

&lt;p&gt;Frequent merges reduce integration conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code reviews become collaborative
&lt;/h2&gt;

&lt;p&gt;Instead of being a bottleneck, reviews become quick feedback loops.&lt;/p&gt;

&lt;p&gt;And perhaps most importantly:&lt;/p&gt;

&lt;p&gt;The team starts focusing on &lt;strong&gt;continuous delivery instead of large feature dumps.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Many teams try to improve their development workflow by introducing new tools, processes, or complex CI pipelines.&lt;/p&gt;

&lt;p&gt;But one of the most effective improvements is surprisingly simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep pull requests small.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Small pull requests reduce friction in every stage of development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code review&lt;/li&gt;
&lt;li&gt;testing&lt;/li&gt;
&lt;li&gt;debugging&lt;/li&gt;
&lt;li&gt;merging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They allow teams to maintain momentum — and momentum is often the most valuable resource in software development.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>software</category>
      <category>ci</category>
      <category>programming</category>
    </item>
    <item>
      <title>Laravel Queue Testing: What Most Developers Get Wrong</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 10 Mar 2026 14:00:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/laravel-queue-testing-what-most-developers-get-wrong-45oj</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/laravel-queue-testing-what-most-developers-get-wrong-45oj</guid>
      <description>&lt;p&gt;Queues are where “it works on my machine” quietly turns into production incidents.&lt;/p&gt;

&lt;p&gt;Emails are sent in the background.&lt;br&gt;
Invoices are generated asynchronously.&lt;br&gt;
External APIs are called outside the request lifecycle.&lt;br&gt;
Data synchronization runs in workers you don’t actively watch.&lt;/p&gt;

&lt;p&gt;Laravel makes queues extremely easy to use. Add ShouldQueue, dispatch the job, done.&lt;/p&gt;

&lt;p&gt;Testing them properly is a different story.&lt;/p&gt;

&lt;p&gt;Most developers stop at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bus::fake();
Bus::assertDispatched(SyncOrderJob::class);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That verifies wiring. It does &lt;strong&gt;not&lt;/strong&gt; verify behavior.&lt;/p&gt;

&lt;p&gt;This article focuses on how to test Laravel jobs correctly — including idempotency, failure handling, and retry safety — with a realistic example.&lt;/p&gt;

&lt;p&gt;Previous article in this category: &lt;a href="https://codecraftdiary.com/2026/02/14/contract-testing-external-apis-in-php-with-pact-real-laravel-example/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2026/02/14/contract-testing-external-apis-in-php-with-pact-real-laravel-example/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scenario: Syncing an Order to an External API
&lt;/h2&gt;

&lt;p&gt;Let’s say we have an Order model that needs to be synchronized to an external system after checkout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Job&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Jobs;use App\Models\Order;
use App\Services\OrderApiClient;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;class SyncOrderJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, SerializesModels;    public int $tries = 3;    public function __construct(
        public Order $order
    ) {}    public function handle(OrderApiClient $client): void
    {
        if ($this-&amp;gt;order-&amp;gt;synced_at !== null) {
            return; // idempotency guard
        }        $client-&amp;gt;send([
            'id' =&amp;gt; $this-&amp;gt;order-&amp;gt;id,
            'total' =&amp;gt; $this-&amp;gt;order-&amp;gt;total,
        ]);        $this-&amp;gt;order-&amp;gt;update([
            'synced_at' =&amp;gt; now(),
        ]);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Already we have several important concerns:&lt;/p&gt;

&lt;p&gt;The job must not sync twice.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It depends on an external API client.&lt;/li&gt;
&lt;li&gt;It may be retried.&lt;/li&gt;
&lt;li&gt;It mutates persistent state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s test it properly&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Testing Dispatch (Integration Level)
&lt;/h2&gt;

&lt;p&gt;This belongs in a feature test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Illuminate\Support\Facades\Bus;
use App\Jobs\SyncOrderJob;

public function test_order_dispatches_sync_job()
{
    Bus::fake();

    $order = Order::factory()-&amp;gt;create();

    $order-&amp;gt;markAsPaid(); // imagine this dispatches the job

    Bus::assertDispatched(SyncOrderJob::class, function ($job) use ($order) {
        return $job-&amp;gt;order-&amp;gt;is($order);
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good. Necessary.&lt;/p&gt;

&lt;p&gt;But insufficient.&lt;/p&gt;

&lt;p&gt;This does not test &lt;em&gt;handle()&lt;/em&gt; at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Testing Job Logic Directly (Unit Level)
&lt;/h2&gt;

&lt;p&gt;Now we test the job itself.&lt;/p&gt;

&lt;p&gt;We do not fake the bus.&lt;br&gt;
We instantiate the job and call handle() directly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mocking the External API
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use App\Services\OrderApiClient;
use Mockery;

public function test_it_calls_external_api_when_not_synced()
{
    $order = Order::factory()-&amp;gt;create([
        'synced_at' =&amp;gt; null,
    ]);    

    $mock = Mockery::mock(OrderApiClient::class);

    $mock-&amp;gt;shouldReceive('send')
        -&amp;gt;once()
        -&amp;gt;with(Mockery::on(fn ($payload) =&amp;gt; $payload['id'] === $order-&amp;gt;id));    

    $job = new SyncOrderJob($order);    

    $job-&amp;gt;handle($mock);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This verifies real behavior.&lt;/p&gt;

&lt;p&gt;We are not testing Laravel.&lt;br&gt;
We are testing our business logic.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Testing Idempotency
&lt;/h2&gt;

&lt;p&gt;Queues retry automatically. If your job is not idempotent, you will create duplicate side effects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function test_it_does_not_call_api_if_already_synced()
{
    $order = Order::factory()-&amp;gt;create([
        'synced_at' =&amp;gt; now(),
    ]);    

    $mock = Mockery::mock(OrderApiClient::class);

    $mock-&amp;gt;shouldNotReceive('send');    

    $job = new SyncOrderJob($order);    

    $job-&amp;gt;handle($mock);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this test fails, your production system will eventually duplicate work.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Testing Retry Safety
&lt;/h2&gt;

&lt;p&gt;Consider a failure between the API call and the database update.&lt;/p&gt;

&lt;p&gt;If the job crashes after calling the API but before marking the order as synced, retry will send it again.&lt;/p&gt;

&lt;p&gt;Safer pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function handle(OrderApiClient $client): void
{
    if ($this-&amp;gt;order-&amp;gt;synced_at !== null) {
        return;
    }    $this-&amp;gt;order-&amp;gt;update([
        'synced_at' =&amp;gt; now(),
    ]);    $client-&amp;gt;send([
        'id' =&amp;gt; $this-&amp;gt;order-&amp;gt;id,
        'total' =&amp;gt; $this-&amp;gt;order-&amp;gt;total,
    ]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now state changes before side effects.&lt;/p&gt;

&lt;p&gt;Let’s simulate a failure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function test_it_retries_safely()
{
    $order = Order::factory()-&amp;gt;create([
        'synced_at' =&amp;gt; null,
    ]);

    $mock = Mockery::mock(OrderApiClient::class);
    $mock-&amp;gt;shouldReceive('send')
        -&amp;gt;once()
        -&amp;gt;andThrow(new RuntimeException());

    $job = new SyncOrderJob($order);

    try {
        $job-&amp;gt;handle($mock);
    } catch (RuntimeException $e) {
        // expected
    }

    $order-&amp;gt;refresh();

    $this-&amp;gt;assertNotNull($order-&amp;gt;synced_at);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now even if retry happens, the idempotency guard prevents double execution.&lt;/p&gt;

&lt;p&gt;This is not theoretical. This is how duplicate invoices happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Testing Jobs That Dispatch Other Jobs
&lt;/h2&gt;

&lt;p&gt;Chained or nested jobs add complexity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ProcessPaymentJob::dispatch($order);
SendInvoiceJob::dispatch($order);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test that correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bus::fake();

$job = new ProcessPaymentJob($order);

$job-&amp;gt;handle($paymentService);

Bus::assertDispatched(SendInvoiceJob::class);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We test &lt;em&gt;handle()&lt;/em&gt; directly.&lt;/li&gt;
&lt;li&gt;We fake the bus only to assert nested dispatch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation is critical.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Testing Backoff and Retry Configuration
&lt;/h2&gt;

&lt;p&gt;Laravel allows configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public int $tries = 5;

public function backoff(): array
{
    return [10, 30, 60];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can test configuration explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function test_job_has_correct_retry_settings()
{
    $job = new SyncOrderJob(Order::factory()-&amp;gt;make());

    $this-&amp;gt;assertEquals(3, $job-&amp;gt;tries);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not glamorous — but configuration errors cause real outages.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The Dangerous Anti-Pattern
&lt;/h2&gt;

&lt;p&gt;This is common:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue::fake();

SyncOrderJob::dispatch($order);

Queue::assertPushed(SyncOrderJob::class);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test will pass even if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The job throws immediately.&lt;/li&gt;
&lt;li&gt;The API client is broken.&lt;/li&gt;
&lt;li&gt;The logic is inverted.&lt;/li&gt;
&lt;li&gt;Idempotency is missing.&lt;/li&gt;
&lt;li&gt;The job deletes the order by accident.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You tested dispatch, not behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use &lt;em&gt;Bus::fake()&lt;/em&gt; vs Real Execution
&lt;/h2&gt;

&lt;p&gt;Use &lt;em&gt;Bus::fake()&lt;/em&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Testing controllers or services that dispatch jobs.&lt;/li&gt;
&lt;li&gt;Verifying orchestration.&lt;/li&gt;
&lt;li&gt;Ensuring a job is queued under certain conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not use &lt;em&gt;Bus::fake()&lt;/em&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Testing job business logic.&lt;/li&gt;
&lt;li&gt;Testing external integration behavior.&lt;/li&gt;
&lt;li&gt;Testing failure handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In job tests, instantiate and call &lt;em&gt;handle()&lt;/em&gt; directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced: Running Jobs Synchronously in Tests
&lt;/h2&gt;

&lt;p&gt;Laravel allows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config(['queue.default' =&amp;gt; 'sync']);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This executes jobs immediately.&lt;/p&gt;

&lt;p&gt;Useful for feature tests where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want full behavior executed.&lt;/li&gt;
&lt;li&gt;You don’t care about queue infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But be careful:&lt;/p&gt;

&lt;p&gt;If you rely on sync execution everywhere, you may miss race conditions or retry issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Principles for Testable Jobs
&lt;/h2&gt;

&lt;p&gt;If your job is hard to test, it’s usually poorly designed.&lt;/p&gt;

&lt;p&gt;Good Laravel jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inject services via handle().&lt;/li&gt;
&lt;li&gt;Keep constructors lightweight.&lt;/li&gt;
&lt;li&gt;Avoid heavy logic in __construct.&lt;/li&gt;
&lt;li&gt;Avoid static service calls.&lt;/li&gt;
&lt;li&gt;Are idempotent by design.&lt;/li&gt;
&lt;li&gt;Fail loudly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bad Laravel jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Call Http::post() directly inside handle.&lt;/li&gt;
&lt;li&gt;Query models statically without abstraction.&lt;/li&gt;
&lt;li&gt;Mutate global state.&lt;/li&gt;
&lt;li&gt;Swallow exceptions silently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing reveals architecture quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Queues introduce three new axes of complexity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Time (execution is delayed)&lt;/li&gt;
&lt;li&gt;Failure (automatic retries)&lt;/li&gt;
&lt;li&gt;Concurrency (multiple workers)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Testing Laravel jobs properly means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Testing dispatch at feature level.&lt;/li&gt;
&lt;li&gt;Testing handle() directly at unit level.&lt;/li&gt;
&lt;li&gt;Mocking external services.&lt;/li&gt;
&lt;li&gt;Verifying idempotency.&lt;/li&gt;
&lt;li&gt;Simulating failure.&lt;/li&gt;
&lt;li&gt;Thinking explicitly about retry safety.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Queues scale your application.&lt;/p&gt;

&lt;p&gt;Tests make your background processing reliable.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>laravel</category>
      <category>php</category>
      <category>testing</category>
    </item>
    <item>
      <title>Singleton Pattern in PHP: Refactoring Global State the Right Way</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 03 Mar 2026 14:00:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/singleton-pattern-in-php-refactoring-global-state-the-right-way-1gbl</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/singleton-pattern-in-php-refactoring-global-state-the-right-way-1gbl</guid>
      <description>&lt;p&gt;In many legacy PHP codebases, global state sneaks in quietly.&lt;br&gt;
A config.php file is included everywhere.&lt;br&gt;
A static helper class grows until it becomes a god object.&lt;br&gt;
A database connection lives in a global variable “just for now”.&lt;/p&gt;

&lt;p&gt;At first, it feels convenient.&lt;br&gt;
Later, it becomes untestable, unpredictable, and painful to change.&lt;/p&gt;

&lt;p&gt;Let’s look at how global state often appears in PHP projects, how developers usually try to “fix” it, and how refactoring toward a Singleton can help — but also why Singleton is frequently misused.&lt;/p&gt;

&lt;p&gt;Previously article in this category: &lt;a href="https://codecraftdiary.com/2026/02/07/builder-pattern-in-php/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2026/02/07/builder-pattern-in-php/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Code Smell: Hidden Global State
&lt;/h2&gt;

&lt;p&gt;A typical example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// config.php
return [
    'db_host' =&amp;gt; 'localhost',
    'db_user' =&amp;gt; 'root',
    'db_pass' =&amp;gt; 'secret',
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// bootstrap.php
$config = require 'config.php';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// UserRepository.php
class UserRepository
{
    public function find(int $id): array
    {
        global $config;        $conn = new PDO(
            'mysql:host=' . $config['db_host'],
            $config['db_user'],
            $config['db_pass']
        );        return $conn-&amp;gt;query('SELECT * FROM users WHERE id = ' . $id)-&amp;gt;fetch();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code “works”.&lt;br&gt;
It also hides a dependency.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;UserRepository&lt;/em&gt; depends on configuration and a database connection, but nothing in its API communicates that. The dependency is invisible and implicit.&lt;/p&gt;

&lt;p&gt;This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unpredictable behavior in tests&lt;/li&gt;
&lt;li&gt;coupling between unrelated parts of the system&lt;/li&gt;
&lt;li&gt;accidental mutation of shared state&lt;/li&gt;
&lt;li&gt;code that is hard to reason about&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a classic code smell: &lt;strong&gt;hidden dependencies via global state.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Naive Fix: Static Helper Class
&lt;/h2&gt;

&lt;p&gt;The next evolutionary step in many PHP projects 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;class Config
{
    private static array $data;    public static function load(): void
    {
        self::$data = require 'config.php';
    }    public static function get(string $key): mixed
    {
        return self::$data[$key];
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class UserRepository
{
    public function find(int $id): array
    {
        $conn = new PDO(
            'mysql:host=' . Config::get('db_host'),
            Config::get('db_user'),
            Config::get('db_pass')
        );        return $conn-&amp;gt;query('SELECT * FROM users WHERE id = ' . $id)-&amp;gt;fetch();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks cleaner:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no global&lt;/li&gt;
&lt;li&gt;no messy includes&lt;/li&gt;
&lt;li&gt;centralized config access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But architecturally, nothing really improved.&lt;/p&gt;

&lt;p&gt;This is still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;global state&lt;/li&gt;
&lt;li&gt;hidden dependency&lt;/li&gt;
&lt;li&gt;impossible to replace in tests&lt;/li&gt;
&lt;li&gt;tightly coupled to a static API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We replaced global with a static singleton-like façade.&lt;/p&gt;

&lt;p&gt;This is not refactoring.&lt;br&gt;
This is just changing syntax.&lt;/p&gt;
&lt;h2&gt;
  
  
  Refactoring Toward a Real Singleton
&lt;/h2&gt;

&lt;p&gt;Let’s refactor this step by step into a proper Singleton.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final class Config
{
    private static ?Config $instance = null;
    private array $data;    private function __construct()
    {
        $this-&amp;gt;data = require 'config.php';
    }    public static function getInstance(): Config
    {
        if (self::$instance === null) {
            self::$instance = new Config();
        }        return self::$instance;
    }    public function get(string $key): mixed
    {
        return $this-&amp;gt;data[$key] ?? null;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class UserRepository
{
    public function find(int $id): array
    {
        $config = Config::getInstance();        $conn = new PDO(
            'mysql:host=' . $config-&amp;gt;get('db_host'),
            $config-&amp;gt;get('db_user'),
            $config-&amp;gt;get('db_pass')
        );        return $conn-&amp;gt;query('SELECT * FROM users WHERE id = ' . $id)-&amp;gt;fetch();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is now a “proper” Singleton:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;private constructor&lt;/li&gt;
&lt;li&gt;lazy initialization&lt;/li&gt;
&lt;li&gt;controlled access point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We improved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structure&lt;/li&gt;
&lt;li&gt;encapsulation&lt;/li&gt;
&lt;li&gt;testability (slightly)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But we did not fix the core design issue.&lt;/p&gt;

&lt;p&gt;The dependency is still hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem: Singleton Is Global State in Disguise
&lt;/h2&gt;

&lt;p&gt;A Singleton is not dependency injection.&lt;br&gt;
It is &lt;strong&gt;global state with better manners&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You still cannot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pass different configurations in tests&lt;/li&gt;
&lt;li&gt;swap implementations easily&lt;/li&gt;
&lt;li&gt;reason locally about dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From an architectural perspective, &lt;em&gt;UserRepository&lt;/em&gt; still depends on &lt;em&gt;Config&lt;/em&gt;, but that dependency is invisible in its constructor or method signature.&lt;/p&gt;

&lt;p&gt;This creates tight coupling and invisible control flow.&lt;/p&gt;

&lt;p&gt;The code is cleaner — but not better designed.&lt;/p&gt;
&lt;h2&gt;
  
  
  When Singleton Is Actually Legitimate
&lt;/h2&gt;

&lt;p&gt;Singleton is not evil by definition.&lt;br&gt;
It is evil when used as a default container for everything.&lt;/p&gt;

&lt;p&gt;Legitimate use cases in PHP:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Immutable Application Configuration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final class AppConfig
{
    private static ?AppConfig $instance = null;
    private array $values;    private function __construct(array $values)
    {
        $this-&amp;gt;values = $values;
    }    public static function boot(array $values): void
    {
        self::$instance = new self($values);
    }    public static function getInstance(): AppConfig
    {
        return self::$instance;
    }    public function get(string $key): mixed
    {
        return $this-&amp;gt;values[$key] ?? null;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bootstrapped once, read-only afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Logger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A logger is often a cross-cutting concern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shared&lt;/li&gt;
&lt;li&gt;stateless&lt;/li&gt;
&lt;li&gt;global by nature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Feature Flags / Environment Context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the state is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;immutable&lt;/li&gt;
&lt;li&gt;read-only&lt;/li&gt;
&lt;li&gt;environment-level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Singleton can be acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Singleton Is a Design Smell
&lt;/h2&gt;

&lt;p&gt;Singleton becomes a problem when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it contains mutable domain state&lt;/li&gt;
&lt;li&gt;it replaces proper dependency injection&lt;/li&gt;
&lt;li&gt;it hides real architectural boundaries&lt;/li&gt;
&lt;li&gt;it becomes a service locator&lt;/li&gt;
&lt;li&gt;business logic starts living inside it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your Singleton has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20 methods&lt;/li&gt;
&lt;li&gt;multiple responsibilities&lt;/li&gt;
&lt;li&gt;domain logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…you created a god object with a private constructor.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Refactoring Path
&lt;/h2&gt;

&lt;p&gt;Often, Singleton should be treated as a &lt;strong&gt;transitional refactoring step&lt;/strong&gt;, not a final architecture.&lt;/p&gt;

&lt;p&gt;Better end state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class UserRepository
{
    public function __construct(
        private PDO $connection
    ) {}    public function find(int $id): array
    {
        return $this-&amp;gt;connection
            -&amp;gt;query('SELECT * FROM users WHERE id = ' . $id)
            -&amp;gt;fetch();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And composition happens at the boundary of the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$config = Config::getInstance();$pdo = new PDO(
    'mysql:host=' . $config-&amp;gt;get('db_host'),
    $config-&amp;gt;get('db_user'),
    $config-&amp;gt;get('db_pass')
);$repo = new UserRepository($pdo);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dependencies are explicit&lt;/li&gt;
&lt;li&gt;testing is trivial&lt;/li&gt;
&lt;li&gt;coupling is controlled&lt;/li&gt;
&lt;li&gt;architecture is visible&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Singleton is not a goal.&lt;br&gt;
It is a compromise.&lt;/p&gt;

&lt;p&gt;It can be a useful refactoring step when removing global state from a legacy PHP codebase.&lt;br&gt;
It can make dependencies slightly more explicit.&lt;br&gt;
It can improve encapsulation.&lt;/p&gt;

&lt;p&gt;But if you stop there, you only replaced chaos with a nicer-looking global variable.&lt;/p&gt;

&lt;p&gt;Good design does not start with patterns.&lt;br&gt;
It starts with &lt;strong&gt;making dependencies explicit&lt;/strong&gt; and &lt;strong&gt;pushing composition to the edges of your system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Singleton is a tool.&lt;br&gt;
Use it deliberately — and sparingly.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwareengineering</category>
      <category>php</category>
      <category>refactoring</category>
    </item>
    <item>
      <title>Why “Just One More Quick Fix” Destroys Your Delivery Pipeline</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 24 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/why-just-one-more-quick-fix-destroys-your-delivery-pipeline-4927</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/why-just-one-more-quick-fix-destroys-your-delivery-pipeline-4927</guid>
      <description>&lt;p&gt;Every development team has heard this sentence:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Let’s just push one more quick fix.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It sounds harmless. Responsible, even. Something small broke, a customer is affected, production is slightly off — and the fix is obvious. Five minutes of work. No need to go through the full process. No need to wait for code review. No need to bother QA. Just one tiny change to keep things moving.&lt;/p&gt;

&lt;p&gt;The problem is not the quick fix itself.&lt;br&gt;
The problem is the culture that grows around it.&lt;/p&gt;

&lt;p&gt;Over time, “just one more quick fix” becomes the default way changes enter production. And once that happens, your delivery pipeline slowly collapses — not because anyone is incompetent, but because the system you built to protect quality gets bypassed piece by piece.&lt;/p&gt;

&lt;p&gt;Previous article in this category: &lt;a href="https://codecraftdiary.com/2026/01/31/why-almost-done-work-breaks-development-flow/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2026/01/31/why-almost-done-work-breaks-development-flow/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Quick Fixes Become the Normal Path to Production
&lt;/h2&gt;

&lt;p&gt;Most teams don’t start with bad intentions. The first few quick fixes usually happen under real pressure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A critical bug blocks a customer.&lt;/li&gt;
&lt;li&gt;A payment fails.&lt;/li&gt;
&lt;li&gt;An integration with a third-party API breaks overnight.&lt;/li&gt;
&lt;li&gt;A configuration mistake takes down part of production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this stage, bypassing the process feels justified. You skip review because “it’s just a one-line change.” You deploy manually because the CI pipeline takes too long. You test in production because the bug only reproduces there.&lt;/p&gt;

&lt;p&gt;Nothing explodes. The fix works. The customer is happy. The team moves on.&lt;/p&gt;

&lt;p&gt;That success teaches the wrong lesson.&lt;/p&gt;

&lt;p&gt;The next time something small breaks, the team remembers:&lt;br&gt;
“We fixed it quickly last time. Let’s do it the same way.”&lt;/p&gt;

&lt;p&gt;After a few months, you end up with two delivery paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The official pipeline: PR → review → tests → CI → deploy&lt;/li&gt;
&lt;li&gt;The real pipeline: Slack message → quick commit → direct deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second one becomes the faster, socially accepted option. The first one starts to feel like bureaucracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Damage: You Don’t See the Cost Immediately
&lt;/h2&gt;

&lt;p&gt;The real damage of quick fixes is not in the individual changes. It’s in what they do to your system over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Your CI Pipeline Stops Being Trustworthy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once people regularly bypass CI, the pipeline stops reflecting reality. Builds fail for reasons nobody prioritizes fixing. Tests become flaky and get ignored. Warnings pile up.&lt;/p&gt;

&lt;p&gt;Eventually, developers stop trusting the pipeline at all.&lt;/p&gt;

&lt;p&gt;At that point, CI is no longer a quality gate — it’s just a ritual.&lt;br&gt;
Teams running on platforms like GitHub or GitLab often reach this state not because the tools are bad, but because the workflow culture quietly degrades around them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Code Reviews Become Optional (Which Means Useless)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When urgent fixes bypass review, reviews lose their authority. Review becomes something you do for “normal work,” while “important work” skips it.&lt;/p&gt;

&lt;p&gt;That’s a subtle but dangerous shift.&lt;/p&gt;

&lt;p&gt;You’re effectively teaching the team that quality checks are less important when stakes are higher. Over time, this creates blind spots exactly where you need discipline the most: production-critical paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. You Normalize Risk Without Noticing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every quick fix that works without immediate consequences normalizes risk-taking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploying without review becomes acceptable.&lt;/li&gt;
&lt;li&gt;Deploying without tests becomes acceptable.&lt;/li&gt;
&lt;li&gt;Deploying without understanding side effects becomes 
acceptable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The absence of incidents is interpreted as proof that the process is unnecessary — until the day it fails in a way that costs real money, data, or trust.&lt;/p&gt;

&lt;p&gt;By the time that incident happens, the delivery pipeline is already too eroded to protect you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Psychological Trap: Quick Fixes Feel Like Ownership
&lt;/h2&gt;

&lt;p&gt;Quick fixes often feel good on a personal level.&lt;/p&gt;

&lt;p&gt;You fixed a production issue fast.&lt;br&gt;
You unblocked a customer.&lt;br&gt;
You were the hero who saved the day.&lt;/p&gt;

&lt;p&gt;This creates a subtle incentive problem: the system rewards individuals for bypassing the process. The delivery pipeline, which exists to protect the team and the product, becomes an obstacle to individual heroics.&lt;/p&gt;

&lt;p&gt;Over time, teams drift into a culture where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Being fast matters more than being safe.&lt;/li&gt;
&lt;li&gt;Fixing symptoms is rewarded more than fixing root causes.&lt;/li&gt;
&lt;li&gt;Short-term relief beats long-term stability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how delivery pipelines rot — not from neglect, but from well-meaning people optimizing for speed under pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Pattern: The “Temporary” Hotfix That Becomes Permanent
&lt;/h2&gt;

&lt;p&gt;A common pattern looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A bug appears in production.&lt;/li&gt;
&lt;li&gt;Someone applies a quick fix directly in production or through a rushed commit.&lt;/li&gt;
&lt;li&gt;The fix is labeled “temporary.”&lt;/li&gt;
&lt;li&gt;The team plans to clean it up later.&lt;/li&gt;
&lt;li&gt;Later never comes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Weeks later, another change interacts with that temporary fix and breaks something else. Now nobody fully understands the code path. The pipeline didn’t catch it because the fix never went through proper tests.&lt;/p&gt;

&lt;p&gt;The original quick fix saved 30 minutes.&lt;br&gt;
The long-term cost is measured in hours of debugging, fragile code, and lost confidence in the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Destroys Flow (Not Just Quality)
&lt;/h2&gt;

&lt;p&gt;Quick fixes don’t just hurt quality. They destroy delivery flow.&lt;/p&gt;

&lt;p&gt;Once bypassing the pipeline becomes normal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers hesitate to touch fragile areas.&lt;/li&gt;
&lt;li&gt;Releases become unpredictable.&lt;/li&gt;
&lt;li&gt;Small changes cause big side effects.&lt;/li&gt;
&lt;li&gt;People slow down because they don’t trust the system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ironically, the behavior that started as “moving faster” ends up making delivery slower and more stressful.&lt;/p&gt;

&lt;p&gt;You trade short-term speed for long-term friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Fix the Culture Without Becoming Bureaucratic
&lt;/h2&gt;

&lt;p&gt;This isn’t about banning hotfixes. Production incidents are real. Speed matters. The goal is to make &lt;em&gt;fast&lt;/em&gt; changes also &lt;em&gt;safe&lt;/em&gt; changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Make the Fast Path the Safe Path&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your CI takes 40 minutes, people will bypass it.&lt;br&gt;
If your review process blocks urgent fixes, people will bypass it.&lt;/p&gt;

&lt;p&gt;Your pipeline must be optimized for speed in emergencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lightweight reviews for hotfixes.&lt;/li&gt;
&lt;li&gt;Fast CI path for critical changes.&lt;/li&gt;
&lt;li&gt;Clear “hotfix” workflow that still enforces minimal quality gates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Track Bypasses as Technical Debt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every bypass should leave a visible trace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A ticket to add tests later.&lt;/li&gt;
&lt;li&gt;A follow-up task to clean up the quick fix.&lt;/li&gt;
&lt;li&gt;A post-incident note explaining what was skipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If bypassing the pipeline leaves no trace, it becomes invisible debt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Reward Stability, Not Heroics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Publicly recognize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fixes that go through the proper pipeline under pressure.&lt;/li&gt;
&lt;li&gt;Improvements to CI speed.&lt;/li&gt;
&lt;li&gt;Reductions in flaky tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stop glorifying the person who “just pushed a fix to prod” without safeguards. You want to reward people who improve the system, not just fight its symptoms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Insight
&lt;/h2&gt;

&lt;p&gt;“Just one more quick fix” is never just one.&lt;/p&gt;

&lt;p&gt;It’s a small exception that rewires how your team relates to the delivery pipeline.&lt;br&gt;
It teaches people what the real process is — not the one in documentation, but the one that gets things shipped.&lt;/p&gt;

&lt;p&gt;If your real process bypasses quality gates under pressure, then under real pressure, you don’t have a delivery pipeline at all.&lt;/p&gt;

&lt;p&gt;You have hope and heroics.&lt;/p&gt;

&lt;p&gt;And that’s not a strategy.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>development</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Contract Testing External APIs in PHP with Pact (Real Laravel Example)</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Wed, 18 Feb 2026 14:05:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/contract-testing-external-apis-in-php-with-pact-real-laravel-example-o3g</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/contract-testing-external-apis-in-php-with-pact-real-laravel-example-o3g</guid>
      <description>&lt;p&gt;Testing integrations with external APIs is one of the most fragile parts of any web application. In theory, we write tests, mock HTTP clients, and feel confident. In practice, APIs change, fields disappear, status codes shift, and production breaks anyway.&lt;/p&gt;

&lt;p&gt;I learned this the hard way.&lt;/p&gt;

&lt;p&gt;In one project, all my tests were green. My mocks returned exactly what I expected. A week later, the external API removed one field from the response. My mocks didn’t know about the change. Production did.&lt;/p&gt;

&lt;p&gt;Mocks told me everything was fine. Reality disagreed.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of problem &lt;strong&gt;contract testing&lt;/strong&gt; is meant to solve. In this article, I’ll show you how to use contract testing for external APIs in PHP with &lt;strong&gt;Pact&lt;/strong&gt;, using a real-world *&lt;em&gt;Laravel *&lt;/em&gt; example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Mocking External APIs Is Not Enough
&lt;/h2&gt;

&lt;p&gt;Mocking external APIs is useful. I still do it. It makes tests fast, deterministic, and cheap. But mocks have one fatal flaw:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;They only test your assumptions about the API, not the API itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Typical problems I’ve seen in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The API removes or renames a field.&lt;/li&gt;
&lt;li&gt;A nested structure changes shape.&lt;/li&gt;
&lt;li&gt;The API starts returning 404 instead of 200 in some edge cases.&lt;/li&gt;
&lt;li&gt;The API adds a required field to the request body.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your mocks will never catch this unless you manually update them. That means mocks alone can silently drift away from reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Contract Testing (In Plain English)
&lt;/h2&gt;

&lt;p&gt;Contract testing sits between mocking and full integration tests.&lt;/p&gt;

&lt;p&gt;Instead of saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This is what I think the API returns,”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“This is the contract between my app (consumer) and the API (provider).”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With consumer-driven contract testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;consumer&lt;/strong&gt;&lt;br&gt;
defines what it expects from the API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;provider&lt;/strong&gt; must verify that it actually fulfills this contract.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the provider changes something that breaks the contract, tests fail &lt;strong&gt;before production breaks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This turns breaking API changes from a runtime surprise into a build-time failure.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Diagram: How contract testing works between a Laravel consumer, Pact mock server, and the real API provider.&lt;/em&gt;&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%2Fc61ic5cgcr1sokhuhen8.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%2Fc61ic5cgcr1sokhuhen8.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Scenario: Laravel as API Consumer
&lt;/h2&gt;

&lt;p&gt;Let’s assume a Laravel application integrates with an external billing API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/customers/{id}

Response:
{
  "id": 123,
  "email": "john@example.com",
  "is_active": true
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your Laravel service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class BillingClient
{
    public function getCustomer(int $id): array
    {
        $response = Http::get("https://billing.example.com/api/customers/{$id}");

        return $response-&amp;gt;json();
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application relies on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;email&lt;/li&gt;
&lt;li&gt;is_active&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the provider removes &lt;em&gt;is_active&lt;/em&gt;, your app breaks. Mocks won’t catch it. Contract testing will.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step: Contract Testing with Pact in Laravel
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Installing Pact for PHP&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install Pact dependencies for your test environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require --dev pact-foundation/pact-php

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll also need the Pact CLI running locally or in CI (usually via Docker).&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the Consumer Contract Test
&lt;/h2&gt;

&lt;p&gt;The goal: define what your Laravel app &lt;strong&gt;expects&lt;/strong&gt; from the API.&lt;/p&gt;

&lt;p&gt;Example consumer test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function test_it_fetches_customer_from_billing_api()
{
    $builder = new InteractionBuilder();

    $builder
        -&amp;gt;given('Customer 123 exists')
        -&amp;gt;uponReceiving('A request for customer 123')
        -&amp;gt;with([
            'method' =&amp;gt; 'GET',
            'path'   =&amp;gt; '/api/customers/123',
        ])
        -&amp;gt;willRespondWith([
            'status' =&amp;gt; 200,
            'headers' =&amp;gt; ['Content-Type' =&amp;gt; 'application/json'],
            'body' =&amp;gt; [
                'id' =&amp;gt; 123,
                'email' =&amp;gt; 'john@example.com',
                'is_active' =&amp;gt; true,
            ],
        ]);

    $builder-&amp;gt;verify(function () {
        $client = new BillingClient();
        $customer = $client-&amp;gt;getCustomer(123);

        $this-&amp;gt;assertSame('john@example.com', $customer['email']);
        $this-&amp;gt;assertTrue($customer['is_active']);
    });
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;defines the expected request&lt;/li&gt;
&lt;li&gt;defines the expected response&lt;/li&gt;
&lt;li&gt;generates a &lt;strong&gt;contract file&lt;/strong&gt; (Pact file)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This contract is the &lt;strong&gt;single source of truth&lt;/strong&gt; between consumer and provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying the Contract on the Provider Side
&lt;/h2&gt;

&lt;p&gt;On the provider side, the API team runs Pact verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pact-verifier --provider-base-url=http://localhost:8000 \
 --pact-url=./pacts/billing-consumer.json

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the API no longer returns is_active, verification fails.&lt;/p&gt;

&lt;p&gt;This is the key difference from mocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mocks only test the consumer&lt;/li&gt;
&lt;li&gt;contract testing tests the agreement&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running Contract Tests in CI
&lt;/h2&gt;

&lt;p&gt;This is where contract testing becomes powerful.&lt;/p&gt;

&lt;p&gt;Typical CI flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Consumer tests generate Pact contracts.&lt;/li&gt;
&lt;li&gt;Provider pipeline verifies contracts.&lt;/li&gt;
&lt;li&gt;Pipeline fails if contracts are broken.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This creates a safety net:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;API changes cannot be deployed if they break existing consumers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Common Mistakes with Contract Testing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Over-Specifying Responses&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don’t lock down every field if you don’t need it.&lt;br&gt;
Only define what your app actually uses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only Testing Happy Paths&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Include error contracts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;404 responses&lt;/li&gt;
&lt;li&gt;validation errors&lt;/li&gt;
&lt;li&gt;unauthorized responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not Versioning Contracts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Treat contracts like code. Version them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not Enforcing Contracts in CI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If no pipeline enforces them, contract testing is just documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Contract Testing Is Worth It (and When It’s Overkill)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Contract testing is worth it when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple teams own different services&lt;/li&gt;
&lt;li&gt;APIs evolve frequently&lt;/li&gt;
&lt;li&gt;breaking changes are expensive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It’s probably overkill when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the integration is trivial&lt;/li&gt;
&lt;li&gt;you don’t control the provider&lt;/li&gt;
&lt;li&gt;the API is stable and rarely changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Contract testing is a scalpel, not a hammer. Use it where API stability matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Contract Testing Fits into Your Laravel Testing Strategy
&lt;/h2&gt;

&lt;p&gt;This is how I think about testing layers in real projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests – business logic&lt;/li&gt;
&lt;li&gt;Feature tests – HTTP flows&lt;/li&gt;
&lt;li&gt;Mocks – isolate slow or unstable services&lt;/li&gt;
&lt;li&gt;Contract tests – protect integrations from breaking changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mocks keep your tests fast.&lt;br&gt;
Contract tests keep your integrations honest.&lt;/p&gt;

&lt;p&gt;They solve different problems and work best together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Mocking external APIs is necessary — but it’s not sufficient.&lt;/p&gt;

&lt;p&gt;If your application depends on external services and you’ve ever been surprised by a breaking API change in production, contract testing is the missing safety net.&lt;/p&gt;

&lt;p&gt;You don’t need to contract-test everything. Start with one critical integration. Add one contract. Let your CI enforce it. The first time a breaking change is caught before deployment, contract testing pays for itself.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>testing</category>
      <category>laravel</category>
      <category>php</category>
    </item>
    <item>
      <title>Builder Pattern in PHP/Laravel: Building Clean and Flexible Order Objects</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 10 Feb 2026 14:05:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/builder-pattern-in-phplaravel-building-clean-and-flexible-order-objects-1mk7</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/builder-pattern-in-phplaravel-building-clean-and-flexible-order-objects-1mk7</guid>
      <description>&lt;p&gt;&lt;strong&gt;In practice&lt;/strong&gt;, orders in e-commerce systems often evolve over time, which makes them a perfect candidate for the Builder Pattern.&lt;/p&gt;

&lt;p&gt;Creating orders in an e-commerce application often seems straightforward at first: a customer selects items, adds them to a cart, and checks out. But as your application grows, the &lt;strong&gt;Order class&lt;/strong&gt; can quickly become complex. You might need to handle optional discounts, shipping options, gift wrapping, different payment methods, and more. Before long, your constructor 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;$order = new Order(
    $customerId,
    $items,
    $paymentMethod,
    $shippingAddress,
    $discountCode,
    $giftWrap = true,
    $specialInstructions = null
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not only is it hard to read, but it’s also easy to make mistakes when creating orders. &lt;strong&gt;Because of this&lt;/strong&gt;, the Builder Pattern becomes a natural solution. It lets you construct objects step by step, handling optional parameters elegantly while keeping your code readable and maintainable.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore how to apply the Builder Pattern to an &lt;strong&gt;Order class in PHP/Laravel&lt;/strong&gt;, improving readability, testability, and flexibility.&lt;/p&gt;

&lt;p&gt;Previous article in this category: &lt;a href="https://codecraftdiary.com/2026/01/17/factory-method-in-php/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2026/01/17/factory-method-in-php/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Builder Pattern Works for Orders
&lt;/h2&gt;

&lt;p&gt;The Builder Pattern is especially useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A class has &lt;strong&gt;many optional parameters&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The class requires &lt;strong&gt;complex setup or validation&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;avoid long constructors&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;clear, readable, fluent code&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In e-commerce, orders often fit all these criteria. By using a builder, we can separate the construction logic from the &lt;strong&gt;order behavior&lt;/strong&gt;, making it easier to extend and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Designing the Order Class
&lt;/h2&gt;

&lt;p&gt;Let’s start with a simple Order class that represents a finalized order object. We’ll assume that once an order is created, it’s immutable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Order
{
    public function __construct(
        public int $customerId,
        public array $items,
        public string $paymentMethod,
        public string $shippingAddress,
        public ?string $discountCode = null,
        public bool $giftWrap = false,
        public ?string $specialInstructions = null
    ) {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt;, the constructor still takes multiple parameters, some of them optional. While this works, it doesn’t scale well if more options are added in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Creating the Order Builder
&lt;/h2&gt;

&lt;p&gt;We can create an &lt;strong&gt;OrderBuilder&lt;/strong&gt; class to handle object creation step by step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class OrderBuilder
{
    private int $customerId;
    private array $items = [];
    private string $paymentMethod = 'credit_card';
    private string $shippingAddress = '';
    private ?string $discountCode = null;
    private bool $giftWrap = false;
    private ?string $specialInstructions = null;

    public static function create(): self
    {
        return new self();
    }

    public function customerId(int $customerId): self
    {
        $this-&amp;gt;customerId = $customerId;
        return $this;
    }

    public function addItem(array $item): self
    {
        $this-&amp;gt;items[] = $item;
        return $this;
    }

    public function paymentMethod(string $method): self
    {
        $this-&amp;gt;paymentMethod = $method;
        return $this;
    }

    public function shippingAddress(string $address): self
    {
        $this-&amp;gt;shippingAddress = $address;
        return $this;
    }

    public function discountCode(string $code): self
    {
        $this-&amp;gt;discountCode = $code;
        return $this;
    }

    public function giftWrap(bool $flag): self
    {
        $this-&amp;gt;giftWrap = $flag;
        return $this;
    }

    public function specialInstructions(string $instructions): self
    {
        $this-&amp;gt;specialInstructions = $instructions;
        return $this;
    }

    public function build(): Order
    {
        if (empty($this-&amp;gt;customerId)) {
            throw new \InvalidArgumentException('Customer ID is required.');
        }

        if (empty($this-&amp;gt;items)) {
            throw new \InvalidArgumentException('At least one item is required.');
        }

        if (empty($this-&amp;gt;shippingAddress)) {
            throw new \InvalidArgumentException('Shipping address is required.');
        }

        return new Order(
            $this-&amp;gt;customerId,
            $this-&amp;gt;items,
            $this-&amp;gt;paymentMethod,
            $this-&amp;gt;shippingAddress,
            $this-&amp;gt;discountCode,
            $this-&amp;gt;giftWrap,
            $this-&amp;gt;specialInstructions
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice a few key things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fluent interface:&lt;/strong&gt; Each setter returns &lt;em&gt;$this&lt;/em&gt;, allowing chained calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation in build():&lt;/strong&gt; Mandatory fields are checked before creating the &lt;em&gt;Order&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stepwise construction:&lt;/strong&gt; You can add items or set options one by one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Using the Builder
&lt;/h2&gt;

&lt;p&gt;Here’s how creating an order looks &lt;strong&gt;without a builder&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$order = new Order(
    123,
    [['product_id' =&amp;gt; 1, 'quantity' =&amp;gt; 2], ['product_id' =&amp;gt; 5, 'quantity' =&amp;gt; 1]],
    'paypal',
    '123 Main Street, City, Country',
    'DISCOUNT10',
    true,
    'Leave at the front door'
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;As a result&lt;/strong&gt;, it’s hard to read and error-prone.&lt;/p&gt;

&lt;p&gt;With the builder, it becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$order = OrderBuilder::create()
    -&amp;gt;customerId(123)
    -&amp;gt;addItem(['product_id' =&amp;gt; 1, 'quantity' =&amp;gt; 2])
    -&amp;gt;addItem(['product_id' =&amp;gt; 5, 'quantity' =&amp;gt; 1])
    -&amp;gt;paymentMethod('paypal')
    -&amp;gt;shippingAddress('123 Main Street, City, Country')
    -&amp;gt;discountCode('DISCOUNT10')
    -&amp;gt;giftWrap(true)
    -&amp;gt;specialInstructions('Leave at the front door')
    -&amp;gt;build();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Much cleaner and readable.&lt;/li&gt;
&lt;li&gt;Easy to skip optional fields or add more items.&lt;/li&gt;
&lt;li&gt;Validation ensures that you set all required fields.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Integrating with Laravel Jobs or Services
&lt;/h2&gt;

&lt;p&gt;In Laravel, orders are often &lt;strong&gt;processed asynchronously&lt;/strong&gt; using Jobs or Services. Builders make this easier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$order = OrderBuilder::create()
    -&amp;gt;customerId($user-&amp;gt;id)
    -&amp;gt;addItem(['product_id' =&amp;gt; $product-&amp;gt;id, 'quantity' =&amp;gt; 1])
    -&amp;gt;shippingAddress($user-&amp;gt;address)
    -&amp;gt;build();

dispatch(new ProcessOrderJob($order));

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also &lt;strong&gt;create reusable templates&lt;/strong&gt; for common order setups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class OrderBuilderFactory
{
    public static function giftOrder(int $customerId, array $items, string $address): OrderBuilder
    {
        return OrderBuilder::create()
            -&amp;gt;customerId($customerId)
            -&amp;gt;shippingAddress($address)
            -&amp;gt;giftWrap(true)
            -&amp;gt;paymentMethod('credit_card')
            -&amp;gt;addItem(...$items);
    }
}

$order = OrderBuilderFactory::giftOrder($user-&amp;gt;id, $items, $user-&amp;gt;address)-&amp;gt;build();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is perfect when you have &lt;strong&gt;standardized order types&lt;/strong&gt;, like gifts or bulk orders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Advantages of Using the Builder Pattern
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Readability&lt;/strong&gt;: The creation of orders reads like a series of instructions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validation&lt;/strong&gt;: All required fields are checked in one place.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;: Optional fields can be skipped without affecting the rest.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testability&lt;/strong&gt;: Builders can be tested independently from the Order class.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;: Adding new optional features (e.g., gift wrap, promo codes) doesn’t break existing code.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 6: When Not to Use a Builder
&lt;/h2&gt;

&lt;p&gt;You don’t always need builders. Avoid using them if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The class only has 2–3 parameters.&lt;/li&gt;
&lt;li&gt;Objects are simple DTOs without optional parameters.&lt;/li&gt;
&lt;li&gt;Fluent APIs add unnecessary complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small objects, a simple constructor or named constructors may suffice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Summary
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Builder Pattern&lt;/strong&gt; is a simple yet powerful way to manage &lt;strong&gt;complex object creation&lt;/strong&gt; in PHP and Laravel. By separating construction from the object itself, your code becomes &lt;strong&gt;readable, maintainable, and testable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For an &lt;em&gt;Order&lt;/em&gt; class with multiple optional features, the builder provides a &lt;strong&gt;clear, step-by-step&lt;/strong&gt; way to create instances without dealing with confusing long constructors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the builder for &lt;strong&gt;complex or optional-heavy objects.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Keep &lt;strong&gt;validation in the build() method.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Leverage &lt;strong&gt;fluent interfaces&lt;/strong&gt; for readability.&lt;/li&gt;
&lt;li&gt;Consider &lt;strong&gt;factory methods for reusable templates&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the Builder Pattern, your order creation code can grow with your application—without turning into a mess of parameters and confusion.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>php</category>
      <category>laravel</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why "Almost Done" Work Breaks Development Flow</title>
      <dc:creator>CodeCraft Diary</dc:creator>
      <pubDate>Tue, 03 Feb 2026 14:57:00 +0000</pubDate>
      <link>https://forem.com/codecraft_diary_3d13677fb/why-almost-done-work-breaks-development-flow-274h</link>
      <guid>https://forem.com/codecraft_diary_3d13677fb/why-almost-done-work-breaks-development-flow-274h</guid>
      <description>&lt;p&gt;Every team has it.&lt;/p&gt;

&lt;p&gt;A pull request that's "basically finished."&lt;br&gt;
A feature that's "just missing a few small things."&lt;br&gt;
A task marked as almost done for the third week in a row.&lt;/p&gt;

&lt;p&gt;It rarely looks like a problem. There's no failing build, no red alerts, no production outage. And yet, this kind of work is one of the most effective ways to quietly destroy development flow.&lt;/p&gt;

&lt;p&gt;Not by being wrong - but by never being truly finished.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Previous article in this category:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://codecraftdiary.com/2026/01/10/effective-code-reviews/" rel="noopener noreferrer"&gt;https://codecraftdiary.com/2026/01/10/effective-code-reviews/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Almost Done" Really Means
&lt;/h2&gt;

&lt;p&gt;In theory, almost done sounds harmless. In practice, it usually means one or more of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests are missing or incomplete&lt;/li&gt;
&lt;li&gt;Edge cases are known but postponed&lt;/li&gt;
&lt;li&gt;Naming or API design "can be improved later"&lt;/li&gt;
&lt;li&gt;Migration exists, rollback does not&lt;/li&gt;
&lt;li&gt;Documentation is optional… for now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these issues are dramatic on their own. That's why they survive. The problem is that almost done work doesn't behave like work in progress - it behaves like invisible technical inventory.&lt;/p&gt;

&lt;p&gt;It sits in the system.&lt;br&gt;
It consumes attention.&lt;br&gt;
And it blocks flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Illusion of Progress
&lt;/h2&gt;

&lt;p&gt;Half-finished work creates a powerful illusion: movement without &lt;strong&gt;completion&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;From the outside, things look active:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PRs are open&lt;/li&gt;
&lt;li&gt;Commits keep coming&lt;/li&gt;
&lt;li&gt;Sprint boards show progress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But internally, the system is stuck.&lt;/p&gt;

&lt;p&gt;Developers context-switch back to old code.&lt;br&gt;
Reviewers repeatedly re-load the same mental model.&lt;br&gt;
Small unresolved decisions accumulate into cognitive debt.&lt;/p&gt;

&lt;p&gt;Nothing moves forward cleanly, because nothing is allowed to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It's Worse Than Doing Nothing
&lt;/h2&gt;

&lt;p&gt;A task that hasn't been started yet is cheap.&lt;/p&gt;

&lt;p&gt;A task that's almost done is expensive.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because almost done work demands continuous re-entry. Every return requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rebuilding context&lt;/li&gt;
&lt;li&gt;Re-evaluating past decisions&lt;/li&gt;
&lt;li&gt;Re-answering questions that should have been resolved once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not linear cost - it compounds.&lt;/p&gt;

&lt;p&gt;The longer work stays in this state, the more expensive finishing it becomes. At some point, teams stop finishing at all. They work around it instead.&lt;/p&gt;

&lt;p&gt;That's when flow collapses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review Fatigue: The Silent Multiplier
&lt;/h2&gt;

&lt;p&gt;Code reviews amplify this problem dramatically.&lt;/p&gt;

&lt;p&gt;A PR that's nearly ready but not quite there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gets reviewed&lt;/li&gt;
&lt;li&gt;Receives feedback&lt;/li&gt;
&lt;li&gt;Gets partially updated&lt;/li&gt;
&lt;li&gt;Then stalls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When it comes back days later, the reviewer must start over.&lt;/p&gt;

&lt;p&gt;Multiply that by several PRs and several developers, and you get review fatigue - not because reviews are bad, but because they never conclude.&lt;/p&gt;

&lt;p&gt;At that point, reviewers stop caring about quality and focus on getting it merged. Standards drop, resentment grows, and nobody can quite explain why.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Pull Requests Wait, Conflicts Multiply
&lt;/h2&gt;

&lt;p&gt;One of the most visible symptoms of almost done work is a pull request that sits open for too long.&lt;/p&gt;

&lt;p&gt;At first, nothing seems wrong. The code is there. CI is green. Review is "scheduled."&lt;br&gt;
Then time passes.&lt;/p&gt;

&lt;p&gt;While the PR waits, the main branch moves on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New features are merged&lt;/li&gt;
&lt;li&gt;Refactors happen elsewhere&lt;/li&gt;
&lt;li&gt;Dependencies shift subtly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the original author finally comes back, the PR is no longer just waiting for approval - it's out of date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: The PR That Was "Basically Ready"
&lt;/h2&gt;

&lt;p&gt;Imagine a backend pull request that introduces a new endpoint.&lt;/p&gt;

&lt;p&gt;The logic is implemented. Tests exist, but only for the happy path.&lt;br&gt;
The author opens the PR and asks for review.&lt;/p&gt;

&lt;p&gt;The review doesn't happen that day.&lt;/p&gt;

&lt;p&gt;Two days later, another feature touches the same service.&lt;br&gt;
A small refactor is merged. Nothing dramatic.&lt;/p&gt;

&lt;p&gt;When the original PR finally gets reviewed, it now has conflicts.&lt;br&gt;
Tests fail because assumptions changed. The reviewer's comments are no longer fully relevant.&lt;/p&gt;

&lt;p&gt;What was a one-hour review becomes a half-day cleanup.&lt;/p&gt;

&lt;p&gt;Nothing about the code was wrong.&lt;br&gt;
It just waited long enough for the system to move around it.&lt;/p&gt;

&lt;p&gt;That's the real cost of "basically finished" work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Merge Conflicts Are Not a Git Problem
&lt;/h2&gt;

&lt;p&gt;At this point, conflicts appear. And teams often treat them as a tooling issue.&lt;/p&gt;

&lt;p&gt;They are not.&lt;/p&gt;

&lt;p&gt;Merge conflicts are a &lt;strong&gt;process failure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They happen because work was allowed to remain unfinished long enough for the system around it to change. The longer a PR stays open, the higher the probability that it collides with unrelated changes.&lt;/p&gt;

&lt;p&gt;This creates a feedback loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PR waits for review&lt;/li&gt;
&lt;li&gt;Branch diverges&lt;/li&gt;
&lt;li&gt;Conflicts appear&lt;/li&gt;
&lt;li&gt;Author postpones fixing them&lt;/li&gt;
&lt;li&gt;Review is delayed again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PR becomes heavier every day it remains open.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Psychological Cost of Rebase Hell
&lt;/h2&gt;

&lt;p&gt;Long-lived PRs don't just create technical conflicts - they create psychological resistance.&lt;/p&gt;

&lt;p&gt;Developers start associating the PR with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Painful rebases&lt;/li&gt;
&lt;li&gt;Repeated test failures&lt;/li&gt;
&lt;li&gt;Comments they've already addressed once&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So they avoid it.&lt;/p&gt;

&lt;p&gt;"This will take a full afternoon."&lt;br&gt;
"I'll deal with it when I have time."&lt;/p&gt;

&lt;p&gt;That avoidance keeps the work in the almost done state even longer - exactly where it does the most damage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review Latency Is the Real Bottleneck
&lt;/h2&gt;

&lt;p&gt;Teams often blame developers for not finishing work.&lt;/p&gt;

&lt;p&gt;But in many cases, the real issue is review latency.&lt;/p&gt;

&lt;p&gt;If reviews routinely take days:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers stop prioritizing clean completion&lt;/li&gt;
&lt;li&gt;PR size increases ("If it takes days anyway…")&lt;/li&gt;
&lt;li&gt;Conflicts become normal, not exceptional&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, the workflow trains people to leave things unfinished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Short-Lived PRs, Short-Lived Problems
&lt;/h2&gt;

&lt;p&gt;The solution is not hero reviewers or stricter rules. &lt;br&gt;
It's shorter lifetimes.&lt;/p&gt;

&lt;p&gt;A PR that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is reviewed within hours&lt;/li&gt;
&lt;li&gt;Merged the same day&lt;/li&gt;
&lt;li&gt;Or explicitly rejected early&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…does not accumulate conflicts, fatigue, or resentment.&lt;/p&gt;

&lt;p&gt;It either finishes - or it dies quickly.&lt;/p&gt;

&lt;p&gt;Both outcomes preserve flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make Waiting Visible&lt;/strong&gt;&lt;br&gt;
One simple but effective practice is to treat waiting PRs as first-class problems.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Highlight PRs open longer than 48 hours&lt;/li&gt;
&lt;li&gt;Treat "waiting for review" as a blocker, not a state&lt;/li&gt;
&lt;li&gt;Prefer reviewing over starting new work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When waiting becomes visible, it stops being normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flow Breaks Where Work Waits
&lt;/h2&gt;

&lt;p&gt;Conflicts, rebases, and stalled PRs are not isolated issues.&lt;/p&gt;

&lt;p&gt;They are downstream effects of allowing work to stay almost done.&lt;/p&gt;

&lt;p&gt;If you want fewer conflicts, don't fight Git.&lt;br&gt;
Finish work faster - or stop starting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lean Has a Name for This
&lt;/h2&gt;

&lt;p&gt;In Lean thinking, unfinished work is called inventory.&lt;/p&gt;

&lt;p&gt;Inventory is dangerous because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It hides problems&lt;/li&gt;
&lt;li&gt;It ties up resources&lt;/li&gt;
&lt;li&gt;It creates false confidence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Software teams rarely think in these terms, but the parallel is exact.&lt;/p&gt;

&lt;p&gt;Half-finished features are inventory.&lt;br&gt;
Open PRs are inventory.&lt;br&gt;
"Almost done" tasks are inventory.&lt;/p&gt;

&lt;p&gt;And like any excess inventory, they slow the entire system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Causes (That Sound Reasonable)
&lt;/h2&gt;

&lt;p&gt;The most dangerous causes are the ones that feel justified:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Large Pull Requests&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;"Let's do it properly in one go."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Large PRs increase the chance that something remains unresolved - and once they stall, they tend to stay stalled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Vague Definition of Done&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If &lt;em&gt;done&lt;/em&gt; isn't explicit, almost done becomes acceptable.&lt;/p&gt;

&lt;p&gt;Without a shared baseline (tests, docs, rollback, review passed), everyone uses their own internal standard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Deferred Decisions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"We'll clean this up later."&lt;br&gt;
"Let's just ship it first."&lt;br&gt;
Later rarely comes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Backend Teams Feel This More
&lt;/h2&gt;

&lt;p&gt;Backend work has properties that make almost done especially toxic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hidden coupling between systems&lt;/li&gt;
&lt;li&gt;Migrations that can't be half-applied&lt;/li&gt;
&lt;li&gt;APIs that lock in contracts early&lt;/li&gt;
&lt;li&gt;Bugs that surface weeks later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A frontend workaround might be ugly but visible.&lt;br&gt;
A backend shortcut is often invisible - until it explodes.&lt;/p&gt;

&lt;p&gt;That's why backend teams feel "slow" even when they work constantly.&lt;/p&gt;

&lt;p&gt;They're not slow. They're stuck finishing yesterday's almost-done work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Fixes That Actually Help
&lt;/h2&gt;

&lt;p&gt;This is not about discipline or motivation. It's about system design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Define "Done" Ruthlessly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If it's merged, it must include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests&lt;/li&gt;
&lt;li&gt;Review approval&lt;/li&gt;
&lt;li&gt;Clear naming&lt;/li&gt;
&lt;li&gt;Migration safety (if applicable)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anything else is not done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Reduce PR Size&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Smaller PRs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finish faster&lt;/li&gt;
&lt;li&gt;Get reviewed faster&lt;/li&gt;
&lt;li&gt;Fail faster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They either complete - or get killed early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Ban "Almost Done" Language&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This sounds extreme, but language shapes behavior.&lt;br&gt;
Replace:&lt;br&gt;
&lt;em&gt;"It's almost done"&lt;/em&gt;&lt;br&gt;
With:&lt;br&gt;
&lt;em&gt;"It's blocked because X"&lt;/em&gt;&lt;br&gt;
Blocked work is honest. Almost done is deceptive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Prefer Completion Over Throughput&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Shipping fewer things that actually finish beats starting many things that don't.&lt;/p&gt;

&lt;p&gt;Flow comes from completion, not activity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finishing Is a Skill
&lt;/h2&gt;

&lt;p&gt;Many teams optimize for starting work.&lt;br&gt;
Few optimize for finishing it.&lt;/p&gt;

&lt;p&gt;Finishing requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Making decisions uncomfortable early&lt;/li&gt;
&lt;li&gt;Accepting "good enough" instead of perfect&lt;/li&gt;
&lt;li&gt;Closing loops deliberately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It feels slower at first.&lt;br&gt;
Then everything accelerates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Flow Is About Endings
&lt;/h2&gt;

&lt;p&gt;Development flow isn't created by speed, tools, or frameworks.&lt;/p&gt;

&lt;p&gt;It's created by &lt;strong&gt;ending things cleanly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your team feels busy but stuck, don't ask how to move faster.&lt;br&gt;
Ask how much work is almost done - and why it's allowed to stay that way.&lt;/p&gt;

&lt;p&gt;Because unfinished work doesn't just wait.&lt;/p&gt;

&lt;p&gt;It actively slows everything around it.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>software</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
