<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/vendor/feed/atom.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US">
                        <id>https://flareapp.io/feed</id>
                                <link href="https://flareapp.io/feed" rel="self"></link>
                                <title><![CDATA[flareapp.io]]></title>
                    
                                <subtitle></subtitle>
                                                    <updated>2026-04-09T00:00:00+00:00</updated>
                        <entry>
            <title><![CDATA[A minimal "Last used" login option indicator with Alpine.js]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/minimal-last-used-login-option-indicator-alpinejs" />
            <id>https://flareapp.io/minimal-last-used-login-option-indicator-alpinejs</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When you come back to Flare's login page, the sign-in option you used last time has a small &quot;Last used&quot; pill above it. Nothing dramatic, just a quiet nudge toward the option you've used before. I added it recently and was surprised how little code it took: around 25 lines of HTML and AlpineJS, most of which already existed to render the login page. No custom code for managing localstorage or cookies.</p>
<p>The whole thing is held together by Alpine's <a href="https://alpinejs.dev/plugins/persist"><code>$persist</code> plugin</a>. It wraps a reactive variable so its value survives page reloads by mirroring it into localStorage. You declare it inside an <code>x-data</code> block and then forget about it. Reading and writing to it look identical to any other Alpine state variable.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;div</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">x-data</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">{ count: $persist(0) }</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;button</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">x-on:click</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">count++</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF">count: </span><span style="color: #81A1C1">&lt;span</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">x-text</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">count</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;&lt;/span&gt;&lt;/button&gt;</span></span>
<span class="line"><span style="color: #81A1C1">&lt;/div&gt;</span></span>
<span class="line"></span></code></pre>
<p>My first pass at the indicator pill was the direct version. One persisted variable, bound to both the TailwindCSS <code>ring</code> class and the pill's visibility. Click a button, variable updates, everything re-renders.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;div</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">x-data</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">{ last: $persist(&#39;&#39;).as(&#39;last-method&#39;) }</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;button</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">x-on:click</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">last = &#39;google&#39;</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">x-bind:class</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">{ &#39;ring&#39;: last === &#39;google&#39; }</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        Sign in with Google</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">&lt;span</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">x-show</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">last === &#39;google&#39;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF">Last used</span><span style="color: #81A1C1">&lt;/span&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;/button&gt;</span></span>
<span class="line"><span style="color: #81A1C1">&lt;/div&gt;</span></span>
<span class="line"></span></code></pre>
<iframe height="300" style="width: 100%;" scrolling="no" title="simple persisting last used sign in button with alpinejs" src="https://codepen.io/alexvanderbist/embed/YPGJzQv?default-tab=result&theme-id=light" frameborder="no" loading="lazy" allowtransparency="true">
  See the Pen <a href="https://codepen.io/alexvanderbist/pen/YPGJzQv">
  simple persisting last used sign in button with alpinejs</a> by Alex Vanderbist (<a href="https://codepen.io/alexvanderbist">@alexvanderbist</a>)
  on <a href="https://codepen.io">CodePen</a>.
</iframe>
<p>You might've already caught the most glaring issue here: the moment you click Google, the pill immediately activates on the Google button. That's not wrong exactly, it's just not what you want, especially for an external login flow that hasn't been completed yet. The pill is supposed to say &quot;here's what you used <em>last</em> time&quot;.</p>
<p>The fix is to split the variable in two. Keep <code>$persist</code> as the write target, but take a one-time copy into a plain Alpine variable on <code>x-init</code>. The bindings read from the copied variable, which was frozen the moment the page loaded.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;div</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">x-data</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">{</span></span>
<span class="line"><span style="color: #A3BE8C">        recent: $persist(&#39;&#39;).as(&#39;last-method&#39;),</span></span>
<span class="line"><span style="color: #A3BE8C">        displayed: &#39;&#39;,</span></span>
<span class="line"><span style="color: #A3BE8C">    }</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">x-init</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">displayed = recent</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;button</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">x-on:click</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">recent = &#39;google&#39;</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">x-bind:class</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">{ &#39;ring&#39;: displayed === &#39;google&#39; }</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        Sign in with Google</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">&lt;span</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">x-show</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">displayed === &#39;google&#39;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF">Last used</span><span style="color: #81A1C1">&lt;/span&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&lt;/button&gt;</span></span>
<span class="line"><span style="color: #81A1C1">&lt;/div&gt;</span></span>
<span class="line"></span></code></pre>
<iframe height="300" style="width: 100%;" scrolling="no" title="persisting last used sign in button with alpinejs" src="https://codepen.io/alexvanderbist/embed/vEXVYjQ?default-tab=result&theme-id=light" frameborder="no" loading="lazy" allowtransparency="true">
  See the Pen <a href="https://codepen.io/alexvanderbist/pen/vEXVYjQ">
  persisting last used sign in button with alpinejs</a> by Alex Vanderbist (<a href="https://codepen.io/alexvanderbist">@alexvanderbist</a>)
  on <a href="https://codepen.io">CodePen</a>.
</iframe>
<p>Now clicking &quot;Sign in with Google&quot; immediately updates localStorage (so it's the value you'll see next time) but it doesn't touch the <code>displayed</code> variable, so the pill stays where it was. On the next visit, <code>x-init</code> runs again, <code>displayed</code> gets the updated value, and the pill has moved.</p>
<p>The login page also has an email/password form below the social buttons, and I deliberately don't track it with a pill, because it feels like the defautl. But if they <em>used</em> to sign in with Google and then switched back to the email and password login flow, I don't want a stale pill on Google forever either. Thats why a single <code>x-on:submit</code> on the form clears the stored value.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;form</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">x-on:submit</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">recent = &#39;&#39;</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">method</span><span style="color: #ECEFF4">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">POST</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ...</span></span>
<span class="line"><span style="color: #81A1C1">&lt;/form&gt;</span></span>
<span class="line"></span></code></pre>
<p>I like when a touch like this doesn't need a session, an endpoint, or a new column on <code>users</code>. The browser already has localStorage, Alpine already has <code>$persist</code>, and the whole thing just feels elegant like this.</p>
]]>
            </summary>
                                    <updated>2026-04-09T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[How Flare handles Livewire v4's single file components]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-flare-handles-livewire-v4s-single-file-components" />
            <id>https://flareapp.io/how-flare-handles-livewire-v4s-single-file-components</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>One of Livewire v4's features is the ability to create single file components. Instead of splitting your logic across a PHP class and a Blade view, you can now write everything in a single <code>.php</code> file.</p>
<p>It's a fantastic developer experience.</p>
<p>Until something breaks.</p>
<p>When an error occurs inside a single file component, the stack trace points to a compiled file with a hashed name like <code>ab1234cd.php</code>. That's not very helpful when you're trying to figure out what went wrong. We wanted Flare to show you the <em>actual</em> source file at the <em>actual</em> line number, just like it does for regular Livewire components.</p>
<p>Here's how we made that happen.</p>
<h2>The problem with compiled files</h2>
<p>Traditional Livewire components are straightforward for Flare to handle. There's a PHP class and a Blade view. We know how to map both back to their source files.</p>
<p>Single file components are different. Livewire compiles them into anonymous classes with hashed filenames and stores them in the cache directory. When an exception is thrown, the stack trace looks something like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">storage</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">framework</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">cache</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">livewire</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">ab1234cd</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">php</span><span style="color: #81A1C1">:</span><span style="color: #B48EAD">42</span></span>
<span class="line"></span></code></pre>
<p>That path tells you nothing about which component failed. And line 42 in the compiled output won't correspond to line 42 in your source file either.</p>
<h2>Mapping hashes back to source files</h2>
<p>During the compilation phase, Livewire creates a cache mapping component names to their compiled classes.</p>
<p>Flare reads that cache using reflection:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">factory</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">livewire.factory</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reflection</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ReflectionProperty</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">factory</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">resolvedComponentCache</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">resolvedComponents</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reflection</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getValue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">factory</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>The map looks roughly like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">                                                                                               </span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">counter</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">class@anonymous</span><span style="color: #EBCB8B">\x00</span><span style="color: #A3BE8C">/storage/framework/cache/livewire/ab1234cd.php:12$0</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"></span></code></pre>
<p>We then loop over each resolved component. An single file component's class string contains a null byte followed by the compiled file path:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">foreach</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">resolvedComponents</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">name</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">compiledClassPath</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">preg_replace</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;/</span><span style="color: #EBCB8B">:\d</span><span style="color: #81A1C1">+</span><span style="color: #EBCB8B">\$.</span><span style="color: #81A1C1">*$</span><span style="color: #ECEFF4">/&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #88C0D0">substr</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #88C0D0">strpos</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #EBCB8B">\x00</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">+</span><span style="color: #88C0D0"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>At this point we have the hash from the compiled path, but only the component name like <code>counter</code>. We don't know where the source file lives yet. So, we ask Livewire's <code>Finder</code> to resolve that:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sourcePath</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">livewireComponentFinder</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">findSingleFileComponentFile</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">name</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">preg_match</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;/[</span><span style="color: #EBCB8B">\/\\\\</span><span style="color: #ECEFF4">]</span><span style="color: #EBCB8B">(</span><span style="color: #ECEFF4">[</span><span style="color: #EBCB8B">a-f0-9</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">+</span><span style="color: #EBCB8B">)\.php</span><span style="color: #81A1C1">$</span><span style="color: #ECEFF4">/&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">compiledClassPath</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">matches</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">compiledPathMap</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">matches</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">]]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sourcePath</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The end result is a simple reverse map:</p>
<p><code>&quot;ab1234cd&quot; =&gt; &quot;/resources/views/livewire/counter.php&quot;</code></p>
<p>When Flare later encounters a hashed path in a stack trace, it checks this map and swaps in the real file path. The map is built lazily, only when an single file component is present in the current request. If you're not using single file components, none of this code runs.</p>
<h2>Fixing stack traces</h2>
<p>With the reverse map in place, we need to hook it into Flare's stack trace processing. The <code>LaravelStacktraceMapper</code> walks every frame in a stack trace and checks if it points to a compiled view:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">map</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frames</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">Throwable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">throwable</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frames</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">array_map</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Frame</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frame</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #81A1C1">if</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">originalPath</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">viewFrameMapper</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">findCompiledView</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frame</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">))</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #88C0D0">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frame</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">file</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">originalPath</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frame</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">lineNumber</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">viewFrameMapper</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getBladeLineNumber</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #88C0D0">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frame</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">file</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frame</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">lineNumber</span></span>
<span class="line"><span style="color: #88C0D0">            </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #81A1C1">return</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frame</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #ECEFF4">},</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frames</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">parent::</span><span style="color: #88C0D0">map</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frames</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">throwable</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>If a frame's file path matches a hash in our reverse map, we replace it with the real source path.</p>
<p>Next up is fixing the line number, which at this point is still the line number of the compiled Blade component.</p>
<p>Flare already knows how to map compiled Blade line numbers back to source line numbers. We use the same <code>getBladeLineNumber</code> logic that we've always used for regular Blade views. Since an single file component is just a Blade view under the hood, no new line number mapping was needed. We just had to get to the real file path first.</p>
<h2>Tagging traces correctly</h2>
<p>Flare doesn't just fix stack traces. It also records performance traces of your application. A trace is a timeline of everything that happens during a request, broken down into individual spans. Each Livewire component gets its own span, showing when it mounted, hydrated, and rendered.</p>
<p>When a component starts rendering, the <code>LivewireRecorder</code> checks whether it's a single file component and tags the span accordingly:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">isSingleFileComponent</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">livewireComponentFinder</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isSingleFileComponent</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">component</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">attributes</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">livewire.component.name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">component</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">livewire.component.single_file_component</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">isSingleFileComponent</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">isSingleFileComponent</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">attributes</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">livewire.component.class</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>For class-based components, Flare records the class name. For single file components, there is no meaningful class name, so we resolve the source file path from the component name and record that instead:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">componentState</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">isSingleFileComponent</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">viewFile</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">livewireComponentFinder</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">findSingleFileComponentFile</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">component</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getName</span><span style="color: #ECEFF4">()))</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">componentState</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">span</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addAttribute</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">view.file</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">viewFile</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The same happens for view spans. When the <code>ViewRecorder</code> detects that the current view belongs to a single file component, it replaces the compiled file path with the real source file and tags the span:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">singleFileComponentFile</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">livewireComponentFinder</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">findCurrentSingleFileComponentFile</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">singleFileComponentFile</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">file</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">singleFileComponentFile</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">attributes</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">view.is_livewire_single_file_component</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This way, every span in your traces links back to the right file, whether it's a class-based component or an single file component.</p>
<h2>Built for Laravel</h2>
<p>Flare is <em>the</em> error tracker and performance monitoring platform  for Laravel. Every feature we build, including single file component support, is designed to work the way Laravel developers expect.</p>
<p>As the framework evolves, so does Flare. We're committed to being deeply integrated with the Laravel ecosystem, so you never have to wrestle with your tools to understand your application.</p>
]]>
            </summary>
                                    <updated>2026-04-08T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Track frontend errors back to the exact commit]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/track-frontend-errors-back-to-the-exact-commit" />
            <id>https://flareapp.io/track-frontend-errors-back-to-the-exact-commit</id>
            <author>
                <name><![CDATA[Dries]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When Flare's JavaScript SDK reports an error, it includes a version identifier. This version ties the error to a specific sourcemap upload so we can show you clean, readable stacktraces instead of minified code.</p>
<p>By default, our Vite and Webpack plugins generate a random UUID for each build. That gets the job done, but when you're staring at an error in Flare, a version like <code>a3f8e2b1-7c4d-4e9f-b6a1-2d5f8c3e7a9b</code> doesn't tell you much.</p>
<p>There's a better option: use your git commit hash as the version identifier. Instead of a random string, you'll see something like <code>e4a7c3f</code> next to every error. Run <code>git log e4a7c3f</code> and you're looking at the exact code that was deployed when things went wrong.</p>
<p>Let's set it up.</p>
<h2>Getting the commit hash at build time</h2>
<p>Both our Vite and Webpack plugins run during your build process, so you can grab the current commit hash right there in your config file:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">execSync</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">from</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">child_process</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">commitHash</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execSync</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">git rev-parse --short HEAD</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">toString</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">trimEnd</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>This gives you a short, 7-character hash like <code>e4a7c3f</code>. If you'd rather use the full 40-character hash, just drop the <code>--short</code> flag.</p>
<h2>Vite</h2>
<p>Pass the commit hash as the <code>version</code> option in your <code>vite.config.js</code>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">defineConfig</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">from</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">vite</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">execSync</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">from</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">child_process</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">flareSourcemapUploader</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">from</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@flareapp/vite</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">commitHash</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execSync</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">git rev-parse --short HEAD</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">toString</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">trimEnd</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">default</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">defineConfig</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">plugins</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> [</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">flareSourcemapUploader</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">key</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">YOUR-FLARE-PROJECT-KEY</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">version</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">commitHash</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ]</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Every build now tags its sourcemaps with the commit hash, and the Flare client automatically sends that same hash along with every error report.</p>
<h2>Webpack</h2>
<p>For Webpack, the option is called <code>versionId</code>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">execSync</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">require</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">child_process</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">FlareWebpackPluginSourcemap</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">require</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@flareapp/flare-webpack-plugin-sourcemap</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">commitHash</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execSync</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">git rev-parse --short HEAD</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">toString</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">trimEnd</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">module</span><span style="color: #ECEFF4">.</span><span style="color: #8FBCBB">exports</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">plugins</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> [</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">FlareWebpackPluginSourcemap</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">key</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">YOUR-FLARE-PROJECT-KEY</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">versionId</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">commitHash</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    ]</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">devtool</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">hidden-source-map</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>If you're using Laravel Mix, the same plugin works in your <code>webpack.mix.js</code>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">execSync</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">require</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">child_process</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">FlareWebpackPluginSourcemap</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">require</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@flareapp/flare-webpack-plugin-sourcemap</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">commitHash</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execSync</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">git rev-parse --short HEAD</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">toString</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">trimEnd</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">mix</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">webpackConfig</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">plugins</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> [</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">FlareWebpackPluginSourcemap</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">key</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">YOUR-FLARE-PROJECT-KEY</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">versionId</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">commitHash</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        ]</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">sourceMaps</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">hidden-source-map</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<h2>Using CI environment variables</h2>
<p>Most CI providers already expose the commit hash as an environment variable, so you don't need to shell out to git at all:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">commitHash</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">process</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">env</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">GITHUB_SHA</span><span style="color: #ECEFF4">?.</span><span style="color: #88C0D0">slice</span><span style="color: #D8DEE9FF">(</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">7</span><span style="color: #D8DEE9FF">)   </span><span style="color: #616E88">// GitHub Actions</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">process</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">env</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">CI_COMMIT_SHORT_SHA</span><span style="color: #D8DEE9FF">                   </span><span style="color: #616E88">// GitLab CI</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execSync</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">git rev-parse --short HEAD</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">toString</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">trimEnd</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>This uses the CI-provided value when available and falls back to the git command for local builds.</p>
<h2>What this looks like in practice</h2>
<p>Once deployed, every JavaScript error in Flare shows the commit hash that produced the build. You see an error spiking after a deploy, check the version, and you're one <code>git show</code> away from reading the exact diff that caused it.</p>
<p>No more cross-referencing deploy logs or guessing based on timestamps. Just a direct link from error to commit.</p>
]]>
            </summary>
                                    <updated>2026-03-25T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[A unified error debug timeline]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/a-unified-error-debug-timeline" />
            <id>https://flareapp.io/a-unified-error-debug-timeline</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When you're debugging an error, context is everything. Knowing that a query failed is useful, but knowing that it failed right after a Redis cache miss, tells a completely different story.</p>
<p>Until now, the error page showed events grouped by type. You could look at queries, logs or glows, but you had to mentally piece together the order of operations yourself. The debug tab now shows all events on a single, chronological timeline.</p>
<p><img src="https://content.spatie.be/assets/screenshot-15.png" alt="" /></p>
<h2>Adding more context</h2>
<p>Previously Flare could show you the following event types:</p>
<ul>
<li>Queries running on your database</li>
<li>Logs written by your application</li>
<li>Flare specific glows</li>
<li>Livewire components being rendered</li>
<li>The PHP command or job currently running</li>
</ul>
<p>We've extended this list with a few new types:</p>
<p><strong>HTTP client requests</strong> Outgoing HTTP calls your application makes are now visible. We show you what the response code was and the request method.</p>
<p><img src="https://content.spatie.be/assets/screenshot-20.png" alt="" /></p>
<p><strong>Redis commands</strong> Whether it's a GET, SET, LPUSH, or EXPIRE, you'll know exactly what your application asked Redis to do and how long it took.</p>
<p><img src="https://content.spatie.be/assets/screenshot-19.png" alt="" /></p>
<p><strong>Filesystem operations</strong> Reads, writes, deletes: we track what operation happened and which files were used in the process. If something goes wrong, the operation gets marked as &quot;failed&quot;.</p>
<p><img src="https://content.spatie.be/assets/screenshot-18.png" alt="" /></p>
<p><strong>Caching operations</strong> A cache hit or miss can make a world of difference in code paths taken. Information like this might be crucial for debugging issues.</p>
<p><img src="https://content.spatie.be/assets/screenshot-21.png" alt="" /></p>
<h2>How it works</h2>
<p>The newer versions of the Flare clients (spatie/laravel-flare and spatie/flare-client-php starting from v2) were completely rewritten to support our performance tracing feature.</p>
<p>As part of tracing, we started recording a lot more events within your application, so it made sense to also include these when an error happens.</p>
<p>You can configure which events are recorded when an error happens, during tracing, and how many items of each event type are stored. More information can be found in the documentation of our packages.</p>
]]>
            </summary>
                                    <updated>2026-03-11T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[New and improved settings screens]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/new-and-improved-settings-screens" />
            <id>https://flareapp.io/new-and-improved-settings-screens</id>
            <author>
                <name><![CDATA[Dries]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Your settings pages are the kind of thing you don't think about until something frustrates you. A form that doesn't behave the way you expect, navigation that makes you hunt for the right page, or inconsistencies that make you second-guess whether you're in the right place.</p>
<p>We've spent time improving the settings experience across Flare. Account, team and project settings all received a new lick of paint. Nothing groundbreaking, just the same kind of polish you find on performance monitoring.</p>
<h2>Consistent layout, everywhere</h2>
<p>Every settings page now follows the same structure: a sidebar for navigation, clearly grouped sections, and a predictable layout no matter where you are. Whether you're updating your profile, managing API tokens, or configuring notifications for a project, the experience feels the same.</p>
<p><img src="https://content.spatie.be/assets/flare/new-and-improved-settings-screens/account-overview.png" alt="account-overview" /></p>
<h2>Improved forms and flows</h2>
<p>We've revisited the forms across settings to make them more intuitive. Fields are better organised, validation is clearer, and destructive actions like deleting your account or revoking a token are properly guarded with confirmation steps.</p>
<p><img src="https://content.spatie.be/assets/flare/new-and-improved-settings-screens/confirm-dialog.png" alt="confirm-dialog" /></p>
<p>Gone are the confusing toggle components on the security and notification pages.</p>
<p><img src="https://content.spatie.be/assets/flare/new-and-improved-settings-screens/2fa-settings-no-toggle.png" alt="no-toggles" /></p>
<h2>Better navigation</h2>
<p>Account settings and project settings each have their own navigation, so you always know exactly where you are.</p>
<p><img src="https://content.spatie.be/assets/flare/new-and-improved-settings-screens/account-settings-sidebar.png" alt="account-settings-sidebar" /></p>
<p><img src="https://content.spatie.be/assets/flare/new-and-improved-settings-screens/team-settings-sidebar.png" alt="team-settings-sidebar" /></p>
<h2>Small details that add up</h2>
<p>Beyond the structural changes, we've cleaned up spacing, typography, and visual hierarchy throughout. Cards have more breathing room. Actions are easier to spot. The overall feel is calmer and more focused.</p>
<p>These are the kinds of changes that are hard to put into a changelog but easy to feel when you use the product.</p>
<p>Give the new settings a look next time you're in Flare. If you have feedback or spot something we missed, we'd love to hear from you.</p>
]]>
            </summary>
                                    <updated>2026-03-11T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing the Flare CLI]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-the-flare-cli" />
            <id>https://flareapp.io/introducing-the-flare-cli</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>We've just released the Flare CLI: a command-line tool that lets you manage your errors, projects, and performance data without ever leaving your terminal.</p>
<p>You can install it globally via Composer:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">composer</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">global require flareapp/flare-cli</span></span>
<span class="line"></span></code></pre>
<p>After authenticating with your API token, you get access to all the things you'd normally do in the Flare dashboard: triaging and resolving errors, viewing occurrence details, browsing performance metrics for your routes, jobs, queries, and more.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">flare</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">errors:list</span></span>
<span class="line"><span style="color: #88C0D0">flare</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">errors:show {id}</span></span>
<span class="line"><span style="color: #88C0D0">flare</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">errors:resolve {id}</span></span>
<span class="line"><span style="color: #88C0D0">flare</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">performance:routes</span></span>
<span class="line"></span></code></pre>
<p>The CLI was built almost entirely from our OpenAPI specification using our latest package: <a href="https://github.com/spatie/laravel-openapi-cli">spatie/laravel-openapi-cli</a>. My colleague Freek share more about that process in <a href="https://freek.dev/3035-building-a-php-cli-for-humans-and-ai-agents-with-almost-no-hand-written-code">a separate post on his blog</a>. The result is a tool that stays in sync with our API automatically.</p>
<p>One thing we're especially excited about: the CLI also ships with a skill for AI coding agents. That means tools like Claude Code can access your Flare data directly, helping you investigate and fix errors without switching context. We'll dive deeper into that in an upcoming post.</p>
<p>The Flare CLI is available now. Give it a spin and let us know what you think.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-the-flare-cli/flarecli.png" alt="Screenshot of the Flare CLI showing all commands" /></p>
]]>
            </summary>
                                    <updated>2026-03-02T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Let your AI coding agent fix errors and review performance]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/flare-ai-skill" />
            <id>https://flareapp.io/flare-ai-skill</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>In our previous post we introduced the Flare CLI. Today we want to highlight one of its most powerful features: the AI agent skill.</p>
<p>With a single command, you can give AI coding agents like Claude Code, Cursor, or Codex direct access to your Flare data:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">flare</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">install-skill</span></span>
<span class="line"></span></code></pre>
<p>That's it. No MCP server to configure, no separate processes to manage. The skill file gets added to your project and your agent picks it up automatically.</p>
<p>Once installed, your agent can look up errors, analyze stack traces against your local code, triage and resolve issues, and review performance data for your routes, jobs, and queries, all without you leaving the terminal or copy-pasting from a dashboard.</p>
<video controls>
  <source src="https://content.spatie.be/assets/flare/flare-ai-skill/flare-errors.mp4" type="video/mp4">
</video>
<p>It works the other way too. Ask your agent to review the performance of your app, and it'll pull in the data from Flare and give you actionable suggestions.</p>
<video controls>
  <source src="https://content.spatie.be/assets/flare/flare-ai-skill/flare-performance.webm" type="video/webm">
</video>
<p>Install the Flare CLI, run <code>flare install-skill</code> in your project, and see what your agent can do with it.</p>
]]>
            </summary>
                                    <updated>2026-03-02T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[How we accidentally dropped half our traces: a tale of Cloudflare Workers and WAF]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-we-accidentally-dropped-half-our-traces-a-tale-of-cloudflare-workers-and-waf" />
            <id>https://flareapp.io/how-we-accidentally-dropped-half-our-traces-a-tale-of-cloudflare-workers-and-waf</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Yesterday morning we noticed something worrying: our queueing servers were processing far fewer spans than usual. After a few hours of debugging, we traced the problem to an unexpected routing change inside Cloudflare. Here's what happened.</p>
<p>Flare receives spans as part of our <a href="https://flareapp.io/docs/tracing">tracing integration</a>. It shows you exactly what happens within your application. Which queries run, which API requests are made, which views get rendered, and a lot more.</p>
<p>We use Flare to monitor Flare, our staging environment watches over the production environment. That morning, the production <code>/v1/traces</code> endpoint was getting hit about 50% less than usual.</p>
<p>The day before, we had merged a massive PR: a complete rewrite of our trace ingestion logic. Naturally, that's where we started looking. But here's the strange part: 50% of our traces <em>were</em> still being processed correctly. The new code was working half the time. Strange. Very strange.</p>
<h2>Our ingestion pipeline</h2>
<p>To understand what went wrong, here's how trace ingestion works in Flare:</p>
<ol>
<li>A client sends a trace in OpenTelemetry format to <code>ingress.flareapp.io/v1/traces</code></li>
<li>A Cloudflare Worker handles the request. Think AWS Lambda, a function running at the edge. The Worker checks:
<ul>
<li>Is there an API key provided?</li>
<li>Is the API key valid?</li>
<li>Has the API key exceeded its usage quota?</li>
<li>Is the API key being rate limited for sending too much at once?</li>
<li>Is the trace format valid?</li>
</ul>
</li>
<li>If everything checks out, the Worker uploads the trace to R2 (Cloudflare's S3 alternative) and sends a notification to Flare with the filename</li>
<li>That notification is a request to <code>ingress.flareapp.io/api/cloudflare-traces</code>, handled by our load balancer and eventually a Laravel application that queues a job to process the file</li>
<li>A queue worker picks up the job, fetches the file from R2, processes the trace, and stores the spans in ClickHouse</li>
<li>Done, another trace successfully processed</li>
</ol>
<h2>What went wrong</h2>
<p>In step 4, the Cloudflare Worker sends a request to <code>ingress.flareapp.io/api/cloudflare-traces</code> to notify Flare about the new trace. We always assumed that since this request originates from a Worker within the same Cloudflare zone, it would be passed directly to our origin load balancer, bypassing the rest of Cloudflare's infrastructure.</p>
<p>That assumption turned out to be wrong.</p>
<p>Instead of going straight to our origin, Cloudflare routed these requests back through its entire stack, including the WAF (Web Application Firewall). We use Cloudflare's WAF extensively: the entire Flare website, app, and API sit behind it. We have rules to block abusive API keys and, crucially, rate limits.</p>
<p>So what happened is simple: the Worker's internal requests to Flare were being treated as regular external traffic. They hit our rate limits and got dropped. That's why roughly half the traces made it through, we were sitting right at the edge of the rate limit threshold.</p>
<p>The baffling part? This Worker setup has been running unchanged since we launched performance monitoring over a year ago. We have no idea why Cloudflare suddenly started routing these requests differently.</p>
<p>Looking back, we actually noticed a similar but smaller dip in spans around Valentine's weekend. At the time we shrugged it off, people probably had better plans than visiting websites on that weekend. It now looks like Cloudflare briefly rerouted requests that weekend too. Since everything recovered on Monday, nobody investigated.</p>
<h2>How we fixed it</h2>
<p>We applied two fixes.</p>
<p><strong>First</strong>, we changed the internal routing so the Worker notifies Flare through the <code>/v1/traces</code> instead of the <code>/api/cloudflare-traces</code> path. That's the path the worker is catching and we hoped it would start avoiding the WAF entirely. This immediately brought most traces back, but not all of them.</p>
<p><strong>Second</strong>, we added a WAF skip rule. Cloudflare lets you bypass WAF rules when a request originates from a Worker in a specific zone:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">cf</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">worker</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">upstream_zone eq </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">flareapp.io</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<p>We assumed this would be the default behavior. It's not. This rule isn't even supported by Cloudflare's visual rule builder, and there's barely any documentation mentioning it exists. But it works!</p>
<p>After both changes, trace ingestion was back to normal.</p>
<h2>Closing thoughts</h2>
<p>Despite this hiccup, Cloudflare Workers have been a great part of our infrastructure. They've kept Flare's servers safe from all kinds of malicious traffic for over a year, and they'll keep doing that. We're happy with what Cloudflare provides, even if the configuration can surprise you sometimes.</p>
<p>Up next: we're working on the next big thing for Flare, <strong>logging</strong>. While you can already send logs to Flare, we're going to take it to the next level. Stay tuned!</p>
]]>
            </summary>
                                    <updated>2026-02-25T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Require two-factor authentication for your team]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/require-two-factor-authentication-for-your-team" />
            <id>https://flareapp.io/require-two-factor-authentication-for-your-team</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Flare already contains stack traces, request data, and environment details for your application's errors. That's sensitive information — and it makes sense to protect access to it.</p>
<p>That's why we've added the ability for team admins to require two-factor authentication for all team members. Once enabled, every member of your team will need to set up 2FA before they can access any<br />
projects or errors.</p>
<p>You'll find the new toggle in your team's Security settings. When you enable the requirement, a confirmation modal makes sure you're intentional about it. And of course, you'll need to have 2FA enabled on<br />
your own account first before you can require it from others.</p>
<p>Team members who haven't set up 2FA yet will be redirected to their security settings with a clear message explaining what's needed. They won't be able to access anything in the team until they've<br />
completed the setup.</p>
<p>If you're working with a larger team or handling production error data, this is an easy way to add an extra layer of protection to your Flare account.</p>
]]>
            </summary>
                                    <updated>2026-02-10T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing Flare’s next big feature: performance monitoring]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-flares-next-big-feature-performance-monitoring" />
            <id>https://flareapp.io/introducing-flares-next-big-feature-performance-monitoring</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<h2>Introducing Flare's next big feature: performance monitoring</h2>
<p><a href="https://flareapp.io">Flare</a> was introduced on stage at Laracon EU 2019 as the first error tracker built for Laravel. Since then, my team at Spatie has been steadily improving it, adding integrations, better JS support, and lots of smaller quality-of-life updates.</p>
<p>I'm happy to share that our big new feature, <a href="https://flareapp.io/platform/performance-monitoring">Performance Monitoring</a>, is now live for everyone. After <a href="https://flareapp.io/blog/lessons-from-the-deep-end">quite the journey</a> and a lengthy beta, I can really say that Flare is now the all-in-one monitoring tool I've always wanted to use. We kept pricing the same, so you're getting two products (the best error tracker for Laravel apps + performance monitoring) for the price of one.</p>
<h3>Let's take a look at performance monitoring</h3>
<p><img src="https://content.spatie.be/assets/cleanshot-2025-11-06-at-15.56.34@2x.jpg" alt="" /></p>
<p>Performance monitoring gives you a complete view of what's happening in your Laravel app. The dashboard tracks everything: HTTP routes, queued jobs, Artisan commands, and database queries.</p>
<p>Each section shows you response or execution times with clear charts, making it easy to spot slow endpoints at a glance. In the screenshot above, you can see which routes are taking the longest (that login POST at 2,114ms might need some attention), how your background jobs are performing, and which database queries are slowing things down.</p>
<p>The &quot;slowest 5%&quot; metric is particularly useful: it helps you identify those edge cases where performance degrades, even when your averages look fine. You're not just getting numbers either; click through and you'll see the actual route names, controller methods, and SQL queries, so you know exactly where to start optimizing.</p>
<p>Let's click that api/users route.</p>
<p><img src="https://content.spatie.be/assets/cleanshot-2025-11-06-at-15.57.28@2x.jpg" alt="" /></p>
<p>The breakdown section is where things get really useful. You can see exactly which database queries ran during this request, how long each one took, and how often it appeared across your traces. In this example, that user bookings query showed up in 2,280 different requests—so you know it's a heavily-used query worth optimizing. You can also tab over to see external HTTP calls, commands, views, and even Livewire components that were part of the request.</p>
<p><img src="https://content.spatie.be/assets/cleanshot-2025-11-06-at-16.01.21@2x.jpg" alt="" /></p>
<p>Scroll down and you'll see the actual traces. Each one shows you the complete timeline of what happened during that request—from the initial Laravel bootstrap and routing, through middleware, down to every single database query that was executed.<br />
All those queries are clickable. Click one and you'll see the full SQL, execution time, and all the context you need to understand what's happening.</p>
<p>Let's now take a look at jobs.</p>
<p><img src="https://content.spatie.be/assets/cleanshot-2025-11-06-at-16.05.42@2x.jpg" alt="" /></p>
<p>Jobs get the same treatment. You can see execution times for all your queued jobs, and spot the slow ones. Let's click one of them.</p>
<p><img src="https://content.spatie.be/assets/cleanshot-2025-11-06-at-16.11.37@2x.jpg" alt="" /></p>
<p>Click into any job and you'll see which routes triggered it, what queries it ran, and whether it called any external APIs or fired off other commands. The breakdown shows you everything that happened during execution, so if a job is taking longer than expected, you can pinpoint whether it's a slow query, an external API call, or something else entirely.</p>
<p>I hope you get the idea of what kind of info we can display. We have similar screens to display performance data around commands, queries, HTTP calls, views and Livewire Components.</p>
<p>To close off this section, let's look at the Livewire Components view, since we're proud that we could build this one. In this section you can look at the render time of each individual Livewire component. Here is the general graph for the <code>ShipPositionTracker</code> component.</p>
<p><img src="https://content.spatie.be/assets/cleanshot-2025-11-07-at-10.10.25@2x.jpg" alt="" /></p>
<p>On that page, when we scroll a bit down, you can see everything that happened in the request that rendered the component. We can see that the component itself executed a couple of queries and rendered a couple of views.</p>
<p><img src="https://content.spatie.be/assets/cleanshot-2025-11-07-at-10.12.10@2x.jpg" alt="" /></p>
<h2>In closing</h2>
<p>We think <a href="https://flareapp.io/platform/performance-monitoring">performance monitoring</a> is a very powerful addition to Flare. This isn't the only feature we added recently. We also have <a href="https://flareapp.io/docs/flare/general/our-mcp-server">an MCP server</a> that allows you to integrate Flare's error tracking capabilities directly into your AI-powered development workflows.</p>
<p>This isn't the finish line, and we'll continue improving Flare. In addition to applying general polish across our service, we're going to start working on the next big feature: making your application logs viewable and searchable from within the Flare UI.</p>
<p>You can try our proven exception tracker and new performance monitoring right now. Just <a href="https://flareapp.io/register">create your Flare account</a> (we offer a free 10-day trial) to get started.</p>
]]>
            </summary>
                                    <updated>2025-11-06T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Lessons from the deep end]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/lessons-from-the-deep-end" />
            <id>https://flareapp.io/lessons-from-the-deep-end</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Spatie has been part of the Laravel ecosystem for years, releasing over 300 packages, publishing many blog posts and courses, and growing with the community. We started building Flare in 2018 with 7 employees; today we’re 11, still balancing client work with our OSS projects and products like Flare, Mailcoach, and Ray.</p>
<p>20 months ago, we started building Performance Monitoring as Flare’s next big feature, never expecting Laravel’s rapid commercial growth to put us in direct competition with their own tools. This is our honest take on those 20 months went, how we’re adapting to this new reality, and where we’re heading next while staying true to who we are. A dive into the deep end, without knowing how far down it goes.</p>
<h2>Why we jumped</h2>
<p>Many other exception trackers at the time felt bloated and didn’t focus on Laravel. We started building Flare as an easy to use, and context-rich alternative built to support Laravel, the framework we have been using for years at that point. Flare has grown into a tool that we use and love here at Spatie every day. Using our own product meant that we were able to improve it over the years by adding features and context that we were missing. This made both Flare and Ignition (our standalone error page, which was the default in Laravel until 2024) to become more powerful and usable over time.</p>
<p>Early on, we had big ideas about expanding Flare beyond just collecting exceptions from failed requests. We are already collecting rich context for them, why not collect the same data for sampled requests, jobs and commands? With new tracing standards like Open Telemetry emerging from 2019, adding performance monitoring felt like a natural next step.</p>
<p>The turning point came at our booth at Laracon EU 2024. A first for Flare, and to add to the excitement, we were almost placed right next to Sentry. A real eye-opener was that not many attendees had actually heard of Flare or its affiliation with Spatie. This came as a bit of a surprise; apparently we had overestimated our recognition. On top of that, a lot of the conversations we had were more about monitoring and tracing integrations rather than error monitoring. This aligned with our own wishes and ideas for Flare and with our gut feeling validated, we felt highly motivated and full of ideas after the conference. Back at the office, we started working on a proof of concept for Performance Monitoring right away.</p>
<h2>Getting caught in a whirlpool</h2>
<p>My colleague Ruben spent just over two weeks extending the Flare client to collect trace data and cook up a nice dashboard with performance metrics for each route. At this point we were confident we could ship this thing by summer 2024.</p>
<p>Fast forward to February 2025 and nothing has shipped at all. Instead, we’re now moving infrastructure to Kubernetes, adding ClickHouse as a secondary time series database, adding additional ingress endpoints to monitoring data, introducing Open Telemetry support and basically rewriting our entire ingress flow for errors. This wasn’t just a new feature anymore, we were overhauling the complete product. We missed all our previous deadlines, and dedicated all of Flare’s development time to it. Features and improvements to Flare we planned in those 12 months were postponed, and we fell behind on the marketing efforts we had planned after Laracon EU.</p>
<p>Even with our years of experience in building projects for ourselves and for clients, we fell into many classic project management traps: scope creep, underestimating tasks, optimistic planning, losing momentum, perfection paralysis and the &quot;while we're at it&quot; syndrome of refactoring everything we touched. Beyond this, life also got in the way. Ruben, our lead back-end developer for Flare became a dad, and other colleagues moved on to new challenges or had to take a break from Flare. As our team’s energy waned, our momentum faded.</p>
<p>The reality of running a small agency adds another layer of complexity. Spatie still relies heavily on client work, and work on Flare was put on the back burner whenever client deadlines emerged. We always try to balance what our team wants to work on with what the company needs, having to shift focus far more often than we’d like. Ironically, after months wrestling with OTEL data structures and ClickHouse performance tuning, doing client work felt like a breath of fresh air.</p>
<p>At this point, we weren’t so sure anymore when and where we would actually land with Performance Monitoring.</p>
<h2>Unfamiliar waters</h2>
<p>A few months after Laracon EU 2024, things in the ecosystem we’ve been part of for years started changing quickly for us. In May 2024, a PR was made to the Laravel core removing Ignition as the framework’s default error page. September brought a $57M investment into Laravel by Accel, and November revealed Nightwatch as a first-party performance monitoring solution at Laracon AU.</p>
<p>While we always have maintained a good relationship with Laravel, we never had any commercial dealings with them. No secret handshakes, contracts or shared profits. Having Ignition as a default error page meant we could drive more leads to Flare, but also add useful features in the framework like sharing errors and the editor integration. This helped Flare to grow in the long term, despite having no direct commercial ties to Laravel.</p>
<p>With Ignition being gone, Laravel backing Sentry as their preferred error tracker, and launching Nightwatch, it felt like your big brother passed you in a race you only just realised was happening. Our commitment with Laravel as the open-source framework remains unchanged. However, the reality is that we are now working with (and competing against) Laravel being a commercial entity.</p>
<p>We get the push toward a more commercial focus, and competition is only natural. But to be building quietly while our competitor was showing off their product to a wide audience was frustrating. It’s hard to stay excited about a private beta when others are stealing the spotlight.</p>
<h2>To sink or to swim</h2>
<p>These announcements made us look inward and reconsider our plans for Flare. Is now even the right time to build and release performance monitoring, or is it now or never?</p>
<p>I remember difficult discussions we had with the entire Spatie team which had colleagues feeling disappointed both with the situation and the perspective of reconsidering Flare’s future. But at some point we decided to power through. It might’ve been the sunk cost fallacy or stubbornness that led to the decision to not back down, but I think it’s simply because we truly believe we’re building a great product.</p>
<p>Looking back, it's easy to view these developments as setbacks, but they helped us to build something better. While we didn't abandon our initial idea, these new circumstances caused us to make different decisions that improved both our performance monitoring and Flare as a whole:</p>
<ul>
<li>Refactoring everything to be OTEL compliant, believing this will become a key differentiator for Flare</li>
<li>Adding the same in-depth monitoring context to our error reporting data, making existing Flare features even better</li>
<li>Switching to ClickHouse to make performance monitoring more performant</li>
<li>Bringing Zuzana onto support to free up developer time for building</li>
<li>Starting to containerize our infrastructure to enable fully European servers</li>
<li>Most importantly: committing to keep investing time, money and energy into Flare</li>
</ul>
<p>We realize that we should’ve shipped sooner and kept the scope smaller (there are only a million blog posts with this conclusion). But at this point, we’re developing something valuable enough that deserves to exist alongside competing products like Nightwatch, or Sentry. The market is large enough for different approaches to monitoring, and with the $57M investment in Laravel, the ecosystem and community will keep growing.</p>
<h2>Swimming back to shore</h2>
<p>In the short term, we're working hard to make performance monitoring available to all our users. In the longer term, we are adapting to the new reality without abandoning Flare. By not trying to outcompete Laravel or match their resources, we can focus on doing what we do best and take a different approach.</p>
<p><strong>As a small team</strong>, we've eliminated the middleman entirely. Our engineers answer your Flare questions directly, help you integrate with complex legacy applications, and build features based on real conversations with users. When you need support, you're talking to the people who actually wrote the code (or these blog posts).</p>
<p><strong>As a European company</strong>, we bring an actual European perspective on data protection, GDPR compliance, and privacy. We can make decisions quickly based on what's right for our users, not what's commercially most interesting. As a team we also like having European alternatives to software and tools.</p>
<p><strong>As Laravel and OSS contributors</strong>, we've spent years plowing through the framework's internals and building Laravel-specific packages, integrations and projects. Our 300+ packages give us insights and ideas on what to integrate into Flare next and how to do it. And we're not scared of exploring better integrations with other (PHP) frameworks and products.</p>
<p>We continue to invest time and energy in Flare and our marketing efforts. Although we were too quiet in the past and relied too heavily on Laravel and Ignition for growth, we are now moving forward and telling our own story. If nobody knows you exist, it doesn't matter how great your tool is.</p>
<p>In line with adapting, here’s what’s next:</p>
<ul>
<li>
<p><strong>Ignition returns as open source</strong>: Losing Ignition as Laravel’s default error page cost us leads, but also our favourite error page. We’re bringing it back and expanding it with the same rich debugging context we collect for Performance Monitoring.</p>
</li>
<li>
<p><strong>Exploring an open source Flare</strong>: Open source lies at the heart of who we are at Spatie and of our success. Making Flare’s code public will reveal how we grew a “simple feature” into a complete product.</p>
</li>
<li>
<p><strong>Releasing Performance Monitoring for free</strong>: Performance Monitoring is currently in public beta and available to all users. Once released, it will be included for free in every plan. Combined with Flare’s existing error tracking, you're now essentially getting two powerful products for the price of one.</p>
</li>
</ul>
<p>The last 20 months have taught us that jumping in at the deep end isn't always about having perfect timing or ideal conditions. Sometimes, it's about having the conviction to keep swimming when the going gets tough.</p>
]]>
            </summary>
                                    <updated>2025-10-02T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Connect your AI agent to Flare to automatically fix production and performance problems in PHP and Laravel projects]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/connect-your-ai-agent-to-flare-to-automatically-fix-production-and-performance-problems-in-php-and-laravel-projects" />
            <id>https://flareapp.io/connect-your-ai-agent-to-flare-to-automatically-fix-production-and-performance-problems-in-php-and-laravel-projects</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>You can now use our MCP server to connect your AI agent to Flare. This way your AI has all context it needs to diagnose and fix production and performance problems of your PHP, JavaScript and Laravel projects.</p>
<p>In this blog post I’d like to tell you how you can use it, and how it works under the hood.</p>
<h2>Fix production and performance problems automatically using AI</h2>
<p>In case you don't know us yet: Flare is an exception and performance monitoring service. It collects all exceptions and performance data form PHP, JS and Laravel projects. We offer a beautiful UI that helps you and all your fellow humans to analyse and diagnose erros and performance problems.</p>
<p>MCP (Model Context Protocol) is a standardized way for AI models to connect with external data sources and tools.</p>
<p>Through Flare's MCP server, AI can now fetch all information about exceptions that happen on production and use that information to fix the bugs locally.</p>
<p>With the Flare MCP server, you can ask your AI assistant to:</p>
<ul>
<li>&quot;Show me the latest errors from my project&quot;</li>
<li>&quot;Fix the error regarding the PHP syntax error in myfile.php&quot;</li>
<li>&quot;Mark the error as resolved in Flare&quot;</li>
<li>&quot;Fix error ERROR-ID-HERE and mark the error as resolved&quot;</li>
<li>&quot;Show me all slow routes in my app&quot;</li>
<li>&quot;Create a note in Flare with the results of your investigation about this error&quot;</li>
</ul>
<p>Personally, I use <a href="https://www.anthropic.com/claude-code">Claude Code</a> as my little AI programmer helper. Using our MCP server, Claude Code can connect to Flare and fetch all the context around an error.</p>
<p>Here I asked Claude Code fetch the information about the latest exception in Flare for the project, and then I asked the AI to fix it.</p>
<p><img src="https://content.spatie.be/assets/fix-error.png" alt="" /></p>
<p>After half a minute, the Claude Code made the correct fix.</p>
<p>Recently, we also added <a href="https://flareapp.io/performance-monitoring">performance monitoring</a> to Flare. Flare knows all the runtimes of your requests, jobs, commands, jobs, ... all that good stuff.</p>
<p>The AI can use of all that info to analyse the performance.</p>
<p><a href="https://mailcoach.app">Mailcoach</a> is another SaaS we've built, which is also monitored via Flare. Here we asked Claude Code to analyse the performance of the Mailcoach API.</p>
<p><img src="https://content.spatie.be/assets/api-analysis.png" alt="" /></p>
<p>Pretty sweet, right!</p>
<p>Our MCP server can also mark errors as resolved (the AI will probably automatically do this after you’ve instructed it to fix an error 😀). We show this in our AI as well.</p>
<h2>Using our MCP server</h2>
<p>We’ve added a dedicated page in our docs with <a href="https://flareapp.io/docs/flare/general/our-mcp-server">instructions how to use our MCP server with Claude Code and Junie</a>.</p>
<p>If you use an other AI agent, the installation process will likely be very similar.</p>
<h2>How does it work under the hood</h2>
<p>Flare is powered by PHP and Laravel. There seems to be many great MCP libraries in development. For now, we settled on using <a href="https://github.com/php-mcp/laravel">php-mcp/laravel</a>.</p>
<p>Using that package it’s possible to create a tool. Very simply set, a tool is a piece of code that the AI can use to fetch live data. In our case, we’ve created a tool to fetch error data.</p>
<p>Here is the entire <code>search_errors</code> tool that the AI can use.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Mcp</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Project</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Project</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Api</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Resources</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Data</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ErrorApiData</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Mcp</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Concerns</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">LogsMcpUsage</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Search</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ErrorSearch</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Foundation</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Auth</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Access</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">AuthorizesRequests</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">LaravelData</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">PaginatedDataCollection</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SearchErrorsTool</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">AuthorizesRequests</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">LogsMcpUsage</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * Search for errors in a specific project. By default, the search query will be is:unresolved, showing you the errors that are not resolved in this project. You can pass a plain string to the query parameter and we&#39;ll use it as best we can in the error message, class... In combination with the string, you can also add these to the query parameter string to scope the search</span></span>
<span class="line"><span style="color: #616E88">     * - class:QueryException -&gt; filters all QueryException errors or occurrences</span></span>
<span class="line"><span style="color: #616E88">     * - job:App\Jobs\DownloadVideo -&gt; filters all errors or occurrences triggered by the DownloadVideo job</span></span>
<span class="line"><span style="color: #616E88">     * - priority:medium -&gt; filters all errors with a medium priority</span></span>
<span class="line"><span style="color: #616E88">     * - first_seen_at:2020-05-16 -&gt; filters all errors which were first seen on May 16th, 2020</span></span>
<span class="line"><span style="color: #616E88">     * - is:unresolved -&gt; only unresolved errors will be shown</span></span>
<span class="line"><span style="color: #616E88">     * - is:resolved -&gt; only resolved errors will be shown</span></span>
<span class="line"><span style="color: #616E88">     * - is:unsnoozed -&gt; only unsnoozed errors will be shown</span></span>
<span class="line"><span style="color: #616E88">     * - is:snoozed -&gt; only snoozed errors will be shown</span></span>
<span class="line"><span style="color: #616E88">     * - is:recent -&gt; only errors which were seen in the last 24 hours will be shown</span></span>
<span class="line"><span style="color: #616E88">     * - is:handled -&gt; only errors which were marked as handled will be shown</span></span>
<span class="line"><span style="color: #616E88">     * - is:unhandled -&gt; only errors which were not marked as handled will be shown</span></span>
<span class="line"><span style="color: #616E88">     * - type:web -&gt; Filter errors or occurrences by the type of entry point which triggered them. Can be web, job, command</span></span>
<span class="line"><span style="color: #616E88">     * - url:https://flareapp.io -&gt; filters all errors or occurrences triggered on the Flare website</span></span>
<span class="line"><span style="color: #616E88">     * - user_email:info@spatie.be -&gt; filters all errors or occurrences triggered by the user with email info@spatie.be</span></span>
<span class="line"><span style="color: #616E88">     *</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88">  </span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">|</span><span style="color: #81A1C1">int</span><span style="color: #616E88">  $projectId  The id of the project to search errors for</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88">  </span><span style="color: #81A1C1">string</span><span style="color: #616E88">  $searchQuery  The search query, do NOT put the project id here, but pass it to the projectId parameter of the tool</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88">  </span><span style="color: #81A1C1">int</span><span style="color: #616E88">  $perPage  The number of errors to return per page</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@return</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #ECEFF4">|</span><span style="color: #81A1C1">mixed</span><span style="color: #616E88">[]</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">|</span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">projectId</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">searchQuery</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">is:unresolved</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">perPage</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">25</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">page</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Project</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">findOrFail</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">projectId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">authorize</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">see</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">logMcpUsage</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorSearch</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ErrorSearch</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorSearch</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getBuilder</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">searchQuery</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errors</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">jsonPaginate</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">perPage</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ErrorApiData</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">collect</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            items</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            into</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">PaginatedDataCollection</span><span style="color: #81A1C1">::class</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">wrap</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">data</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toArray</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Let’s go over this. The class starts with a big block of comment explaining what the tool does and how the parameters can be used. This information will be seen by AI when connecting to the MCP server. So this docblock is basically the manual for AI on how to use the tool.</p>
<p>You might wonder how security is handled. The AI can’t read our DB directly because the AI itself does not run on our server, it runs on your computer (or on the servers of your favorite AI service). The AI simply performance requests to our MCP endpoint, and all request must be authenticated by a valid API token.</p>
<p>Because we leverage token based authenticated, we can simply keep on using Laravel’s authorization capabilities and make a policy check.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">authorize</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">see</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Because we want to know how people are using our MCP server, we also log all usage</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">logMcpUsage</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>In Flare’s web UI, there’s a search field where people can search for errors. On the backend the search is implemented in the <code>ErrorSearch</code> class. Our MCP server can use the same class for searching.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorSearch</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ErrorSearch</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorSearch</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getBuilder</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">searchQuery</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errors</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">jsonPaginate</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">perPage</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>And finally, we return the found errors as JSON to the AI</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ErrorApiData</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">collect</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    items</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errors</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    into</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">PaginatedDataCollection</span><span style="color: #81A1C1">::class</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">wrap</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">data</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toArray</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<h2>In closing</h2>
<p><a href="https://flareapp.io/docs/flare/general/our-mcp-server">Our MCP server</a> is now in public beta, and we’re looking for people that are willing to help us test it. If you don’t have a Flare account yet, you can create one for free (we have a 10 day trial).</p>
<p>We consider this feature still beta and might change it in the future. Like said above, there are several MCP packages for PHP projects in development, we might swap out our current one if a better one comes along. Also, with MCP servers getting popular there are also several opinons on how it should be built up and which responses it should return. Here's <a href="https://liquidmetal.ai/casesAndBlogs/mcp-api-wrapper-antipattern/">an interesting blog post on that subject</a>.</p>
<p>Several of our close friends have already been using our MCP servers for some time, and <a href="https://x.com/mattiasgeniar/status/1965378190400233789">reported good results</a>. I hope you'll like using Flare's MCP capabilities as well.</p>
]]>
            </summary>
                                    <updated>2025-09-11T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Using ClickHouse at Flare to aggregate data]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-were-using-clickhouse-at-flare-aggregating-data" />
            <id>https://flareapp.io/how-were-using-clickhouse-at-flare-aggregating-data</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>The word is out: we're working on performance monitoring, Flare's most significant feature yet. One of the challenges we've tackled in the previous months was storing all this performance data generated by our customers' applications.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-clickhouse/screenshot-3.png" alt="" /></p>
<p>An application has multiple entry points. Think of requests, jobs, or commands within those entry points. Many things happen within those entry points, like executing queries, rendering views, making external HTTP calls, etc. We call each unit where something happens (including the entry points) a span.</p>
<p>Spans have a start and end time (and thus a duration). They can be children of other spans, like a query happening during a request, and they can be running on different servers at different times, for example, when dispatching and running a job on separate servers.</p>
<p>Ultimately, we want to aggregate these spans into digestible pieces to show, for example, which routes are slow and which queries might be the reason.</p>
<p>To do that, we need to process massive amounts of data quickly. While more traditional databases like MySQL and PostgreSQL allow us to store and query data quickly, they aren't that useful when storing and querying time series data. That's where ClickHouse comes in. A column-store database allows us to digest and query lots of data blazingly fast.</p>
<p>One of ClickHouse's significant advantages is its unique table structures, like the AggregatingMergeTree, which allows us to store all sorts of statistics efficiently about the duration of spans aggregated per minute. Let's take a look at an example.</p>
<p>Say we have the following spans:</p>
<table>
<thead>
<tr>
<th align="left">Span</th>
<th align="left">Duration</th>
<th align="left">Time</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">A</td>
<td align="left">500ms</td>
<td align="left">12:55:13</td>
</tr>
<tr>
<td align="left">B</td>
<td align="left">200ms</td>
<td align="left">12:55:22</td>
</tr>
<tr>
<td align="left">C</td>
<td align="left">150ms</td>
<td align="left">12:56:10</td>
</tr>
<tr>
<td align="left">D</td>
<td align="left">200ms</td>
<td align="left">13:10:05</td>
</tr>
</tbody>
</table>
<p>Our aggregated spans table per minute now would look like this:</p>
<table>
<thead>
<tr>
<th align="left">Time</th>
<th align="left">Spans</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">12:55</td>
<td align="left">A, B</td>
</tr>
<tr>
<td align="left">12:56</td>
<td align="left">C</td>
</tr>
<tr>
<td align="left">13:10</td>
<td align="left">D</td>
</tr>
</tbody>
</table>
<p>Querying all the spans between 12:00 and 13:00 now only requires reading 2 rows instead of 3. That might not seem like a massive improvement, but if you're ingesting +100 spans per minute, that's much less data to read!</p>
<p>While it is technically possible to implement this functionality with traditional databases, it requires a lot of code, caching, and time. The cool thing is that ClickHouse provides this out of the box and even more!</p>
<p>Let's calculate the average duration of the spans in the table. In a more traditional database, we would add a column to the table containing the average duration:</p>
<table>
<thead>
<tr>
<th align="left">Time</th>
<th align="left">Spans</th>
<th align="left">Average</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">12:55</td>
<td align="left">A, B</td>
<td align="left">350ms</td>
</tr>
<tr>
<td align="left">12:56</td>
<td align="left">C</td>
<td align="left">150ms</td>
</tr>
<tr>
<td align="left">13:10</td>
<td align="left">D</td>
<td align="left">200ms</td>
</tr>
</tbody>
</table>
<p>If we want to query the average duration at 12:55, we get 350ms, which is correct. But what if we want to query the average duration for all spans between 12:00 and 13:00?</p>
<p>A naive approach would be summing the averages and dividing that number by two. But that's not correct:</p>
<ul>
<li>Naive average approach: (350 + 150) / 2 = 250</li>
<li>Correct average approach: (500+200+150) / 3 = 283</li>
</ul>
<p>So, to correctly calculate the average, we need the input of the average function instead of the outcome we stored in the database. ClickHouse solves this problem using AggregateFunctions; they allow you to call mathematical functions on data, but instead of storing the outcome, they store the input as a binary blob. When you need the outcome, call the mathematical function again, this time on the binary input data, and get a number as output.</p>
<p>If we would try to represent this in our demo table, it would look like this:</p>
<table>
<thead>
<tr>
<th align="left">Time</th>
<th align="left">Spans</th>
<th align="left">AggregateFunction(average)</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">12:55</td>
<td align="left">A, B</td>
<td align="left">500ms, 200ms</td>
</tr>
<tr>
<td align="left">12:56</td>
<td align="left">C</td>
<td align="left">150ms</td>
</tr>
<tr>
<td align="left">13:10</td>
<td align="left">D</td>
<td align="left">200ms</td>
</tr>
</tbody>
</table>
<h2>SQL side of things</h2>
<p>To implement such an operation, we first need a table with the spans we've received:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">CREATE</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">TABLE</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">spans</span></span>
<span class="line"><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    span_id String,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    duration UInt64,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">time</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">DateTime</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">ENGINE </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> MergeTree</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #81A1C1">ORDER BY</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">time</span><span style="color: #D8DEE9FF">;</span></span>
<span class="line"></span></code></pre>
<p>This schema differs from the schemas we're used to from MySQL and Postgres. Let's quickly go through it!</p>
<ul>
<li>We create a table <code>spans</code> with three columns: <code>span_id</code>, <code>duration</code>, and <code>time</code></li>
<li>Each ClickHouse table requires an engine type. We're using a MergeTree engine, which behaves kinda like a more traditional SQL table</li>
<li>The <code>ORDER BY time</code> clause defines how ClickHouse will store and index data</li>
</ul>
<p>Let's create our <code>aggregated_spans</code> schema where we'll store the aggregate grouped by time:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">CREATE</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">TABLE</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">aggregated_spans</span></span>
<span class="line"><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">time</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">DateTime</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    spans AggregateFunction(groupArray, String),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    average AggregateFunction(avg, UInt64)</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">ENGINE </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> AggregatingMergeTree</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #81A1C1">ORDER BY</span><span style="color: #D8DEE9FF"> toStartOfMinute(</span><span style="color: #81A1C1">time</span><span style="color: #D8DEE9FF">);</span></span>
<span class="line"></span></code></pre>
<p>In this schema:</p>
<ul>
<li>We create a table <code>aggregated_spans</code> with three columns</li>
<li><code>time</code> is a plaintext column representing the date</li>
<li><code>spans</code> is an array of strings representing the span ID, but instead of storing the string itself, we store the input of a group function so that in the end, we can get an array of all the span IDs</li>
<li><code>average</code> stores the input of the average function, as we discussed earlier</li>
<li>Since we're aggregating data, we're using the AggregatingMergeTree table engine</li>
<li>The <code>ORDER BY time</code> clause again tells ClickHouse how to store and index data; it is also the column by which we'll aggregate the data. Since we want to do this on a minute basis, we're stripping the seconds for every datetime.</li>
</ul>
<p>We're using a materialized view to get data from the <code>spans</code> table into the <code>aggregated_spans</code> table. It can be compared to a trigger that listens to inserts on the <code>spans</code> table; when an insert happens, the materialized view query will be executed and insert rows in the <code>aggregated_spans</code> table. We create it as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">CREATE MATERIALIZED VIEW aggregated_spans_mv TO aggregated_spans </span><span style="color: #81A1C1">AS</span></span>
<span class="line"><span style="color: #D8DEE9FF">SELECT </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">toStartOfMinute</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">time</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">AS</span><span style="color: #D8DEE9FF"> time</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">groupArrayState</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">span</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">AS</span><span style="color: #D8DEE9FF"> spans</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">avgState</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">duration</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">AS</span><span style="color: #D8DEE9FF"> avg_duration</span></span>
<span class="line"><span style="color: #D8DEE9FF">FROM spans</span></span>
<span class="line"><span style="color: #D8DEE9FF">GROUP BY time</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Did you notice the <code>groupArrayState</code> and <code>avgState</code> functions? Since we don't want to store the output of the <code>groupArray</code> or <code>avg</code> functions but their input, we suffix <code>state</code> to these functions to do that.</p>
<p>Inserting spans can be done just like you would expect:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">INSERT INTO</span><span style="color: #D8DEE9FF"> spans </span><span style="color: #81A1C1">VALUES</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">A</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">, </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">, </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2024-03-14 12:55:13</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">B</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">, </span><span style="color: #B48EAD">200</span><span style="color: #D8DEE9FF">, </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2024-03-14 12:55:22</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">C</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">, </span><span style="color: #B48EAD">150</span><span style="color: #D8DEE9FF">, </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2024-03-14 12:56:10</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">D</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">, </span><span style="color: #B48EAD">200</span><span style="color: #D8DEE9FF">, </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2024-03-14 13:10:05</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">);</span></span>
<span class="line"></span></code></pre>
<p>The materialized view will automatically act and insert 4 rows into <code>aggregated_spans</code>. If we would now query <code>aggregated_spans</code> like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">from</span><span style="color: #D8DEE9FF"> aggregated_spans</span></span>
<span class="line"></span></code></pre>
<p>Then we'll get the following rows:</p>
<table>
<thead>
<tr>
<th align="left">Time</th>
<th align="left">Spans</th>
<th align="left">Average</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">12:55</td>
<td align="left">BINARY</td>
<td align="left">BINARY</td>
</tr>
<tr>
<td align="left">12:56</td>
<td align="left">BINARY</td>
<td align="left">BINARY</td>
</tr>
<tr>
<td align="left">13:10</td>
<td align="left">BINARY</td>
<td align="left">BINARY</td>
</tr>
</tbody>
</table>
<p>As you can see, the first two rows are aggregated instead of having four rows within our initial data. The values of the span IDs and average are not yet what they should be since they're represented as a binary blob. Let's fix that by adjusting our query:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">time</span><span style="color: #D8DEE9FF">, </span></span>
<span class="line"><span style="color: #D8DEE9FF">    groupArrayMerge(spans) </span><span style="color: #81A1C1">AS</span><span style="color: #D8DEE9FF"> spans, </span></span>
<span class="line"><span style="color: #D8DEE9FF">    avgMerge(avg_duration) </span><span style="color: #81A1C1">AS</span><span style="color: #D8DEE9FF"> avg_duration</span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> aggregated_spans</span></span>
<span class="line"><span style="color: #81A1C1">GROUP BY</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">time</span></span>
<span class="line"><span style="color: #81A1C1">ORDER BY</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">time</span><span style="color: #D8DEE9FF">;</span></span>
<span class="line"></span></code></pre>
<p>Let's quickly go through this query:</p>
<ul>
<li>We'll select the time on which we also group the rows to get the data on a minute basis</li>
<li>We run the <code>groupArray</code> and <code>avg</code> functions, but this time, we suffix them with <code>merge</code>, meaning we provide input values as a binary blob of values instead of raw numbers</li>
<li>In the end, everything will be sorted by the time</li>
</ul>
<p>The output now looks like this:</p>
<table>
<thead>
<tr>
<th align="left">Time</th>
<th align="left">Spans</th>
<th align="left">Average</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">12:55</td>
<td align="left">A,B</td>
<td align="left">350</td>
</tr>
<tr>
<td align="left">12:56</td>
<td align="left">C</td>
<td align="left">150</td>
</tr>
<tr>
<td align="left">13:10</td>
<td align="left">D</td>
<td align="left">200</td>
</tr>
</tbody>
</table>
<p>Looking great! As a last test, can we get the correct average if we combine rows together to achieve, for example, an average per hour? A query for such data would look like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">SELECT</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">    toStartOfHour(</span><span style="color: #81A1C1">time</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">time</span><span style="color: #D8DEE9FF">, </span></span>
<span class="line"><span style="color: #D8DEE9FF">    groupArrayMerge(spans) </span><span style="color: #81A1C1">AS</span><span style="color: #D8DEE9FF"> spans, </span></span>
<span class="line"><span style="color: #D8DEE9FF">    avgMerge(avg_duration) </span><span style="color: #81A1C1">AS</span><span style="color: #D8DEE9FF"> avg_duration</span></span>
<span class="line"><span style="color: #81A1C1">FROM</span><span style="color: #D8DEE9FF"> aggregated_spans</span></span>
<span class="line"><span style="color: #81A1C1">GROUP BY</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">time</span></span>
<span class="line"><span style="color: #81A1C1">ORDER BY</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">time</span><span style="color: #D8DEE9FF">;</span></span>
<span class="line"></span></code></pre>
<p>Notice that we'll now select <code>toStartOfHour(time)</code> instead of <code>time</code>. The results now look like this:</p>
<table>
<thead>
<tr>
<th align="left">Time</th>
<th align="left">Spans</th>
<th align="left">Average</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">12:00</td>
<td align="left">A,B,C</td>
<td align="left">283</td>
</tr>
<tr>
<td align="left">13:00</td>
<td align="left">D</td>
<td align="left">200</td>
</tr>
</tbody>
</table>
<p>Since we're calculating the functions on the spot based on the query, our average is now correct.</p>
<h2>Conclusion</h2>
<p>ClickHouse was essential to crunch massive amounts of data without breaking a sweat. Next time, we'll examine how we're running queries against ClickHouse in Laravel and how to run these queries concurrently in batches.</p>
<p>See ya!</p>
]]>
            </summary>
                                    <updated>2025-04-28T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Automating styles with data-slot]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/automating-styles-with-data-slot" />
            <id>https://flareapp.io/automating-styles-with-data-slot</id>
            <author>
                <name><![CDATA[Sébastien]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>As we work on Flare’s performance monitoring feature, we’ve adopted a method to streamline our component architecture, making our UI more adaptable and maintainable. We derived this approach from Adam Wathan, who gave an insightful presentation titled <a href="https://www.youtube.com/watch?v=MrzrSFbxW7M&amp;ab_channel=Laravel">&quot;Designing a Component Library&quot;</a> at Laracon US 2024. Although it’s been five months since the talk, there were some awesome takeaways. In his presentation, Adam showcased techniques for building flexible and maintainable UI components using Tailwind CSS. One of the key ideas he explored was the use of the <code>data-slot</code> attribute to improve component structure and reusability.</p>
<p>The <code>data-slot</code> attribute allows developers to define placeholders within components that can be dynamically filled with content, reducing the need for excessive props or complex conditional rendering. By selecting elements based on <code>data-slot</code>, we can inject content where needed while keeping components clean and flexible.</p>
<h3><strong>A Practical Example: Button Component with <code>data-slot</code></strong></h3>
<p>Let’s say we have a <strong>button component</strong> that simply renders its children. We assign a fixed height of 40 pixels to the button and set padding of 12 pixels on the left and right.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Button</span><span style="color: #ECEFF4">({</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">children</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">})</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;button</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">className</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flex items-center justify-center gap-3 rounded bg-purple-500 text-white text-semibold px-3 h-[40px]</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">{</span><span style="color: #D8DEE9">children</span><span style="color: #81A1C1">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;/button&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Now, based on this <code>Button</code> component, we can create a button with both text and an icon by passing them as children:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">App</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #8FBCBB">Button</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">			Performance monitoring</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">&lt;</span><span style="color: #8FBCBB">Icon</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;/</span><span style="color: #8FBCBB">Button</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>But what if we want a button with just an icon and no text? Simply removing the text does the job, but now the button's dimensions become inconsistent. Its width and height vary because of the padding, and ideally, we want it to be a square when only an icon is present.</p>
<p>To fix this, we could use the <code>aspect-square</code> class, but how does the <code>Button</code> component know when to apply it?</p>
<h3><strong>The Old Approach: Using a Prop</strong></h3>
<p>Before discovering <code>data-slot</code>, we handled this scenario by adding an explicit <code>icon</code> prop to the <code>Button</code> component:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">App</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #8FBCBB">Button</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">icon</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">&lt;</span><span style="color: #8FBCBB">Icon</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;/</span><span style="color: #8FBCBB">Button</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This worked, but it was tedious. Every time we used an icon-only button, we had to remember to pass the <code>icon</code> prop, something easy to forget, leading to inconsistencies. Ideally, the button should automatically adjust based on its children.</p>
<h2>The <code>data-slot</code> Solution</h2>
<p>Instead of adding a prop, we can mark the <code>Icon</code> component with <code>data-slot=&quot;icon&quot;</code>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Icon</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;span</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">data-slot</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">icon</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Then, we modify the <code>Button</code> component to detect when it only contains an icon:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Button</span><span style="color: #ECEFF4">({</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">children</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">})</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;button</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">className</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flex items-center justify-center gap-3 rounded bg-purple-500 text-white text-semibold px-3 h-[40px] [&amp;:has(&gt;[data-slot=icon]:only-child)]:aspect-square</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">{</span><span style="color: #D8DEE9">children</span><span style="color: #81A1C1">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;/button&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Now, the <code>aspect-square</code> class is <strong>automatically</strong> applied when the button contains only an <code>Icon</code> component!</p>
<h2>Breaking Down the Class Selector</h2>
<p>Let’s analyze this newly added class:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">&amp;:</span><span style="color: #88C0D0">has</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">[</span><span style="color: #88C0D0">data</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">slot</span><span style="color: #81A1C1">=</span><span style="color: #88C0D0">icon</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">:</span><span style="color: #88C0D0">only</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">child</span><span style="color: #ECEFF4">)]</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">aspect</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">square</span></span>
<span class="line"></span></code></pre>
<p>This means:</p>
<ul>
<li><code>&amp;</code>: Selects the current element (the <code>button</code>).</li>
<li><code>:has()</code>: A pseudo-selector that checks if the parent has a specific child.</li>
<li><code>&gt;[data-slot=icon]:only-child</code>: Selects a direct child with the attribute <code>data-slot=&quot;icon&quot;</code> that has no siblings.</li>
</ul>
<h3><strong>Extending the Solution: Handling Padding</strong></h3>
<p>We can go a step further and remove padding when the button has only an icon:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Button</span><span style="color: #ECEFF4">({</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">children</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">})</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> (</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;button</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">className</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flex items-center justify-center gap-3 rounded bg-purple-500 text-white text-semibold h-[40px] [&amp;:has(&gt;[data-slot=icon]:only-child)]:aspect-square [&amp;:not(:has(&gt;[data-slot=icon]:only-child))]:px-3</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">{</span><span style="color: #D8DEE9">children</span><span style="color: #81A1C1">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;/button&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This means:</p>
<ul>
<li>If the button has only an icon, it becomes a square and loses its padding.</li>
<li>If the button has other children, it retains <code>px-3</code> (left and right padding).</li>
</ul>
<h2>Why does this approach rock?</h2>
<p>This approach eliminates the need for extra props, allowing the button to adapt automatically and reducing unnecessary API surface. The JSX remains cleaner since there’s no need to manually specify button types or styles. With CSS handling the logic, components become more declarative and easier to maintain.</p>
<h2>A Potential Downside: Class Complexity</h2>
<p>While this approach is powerful, it can result in long, hard-to-read class names. To improve readability, we use the <a href="https://www.npmjs.com/package/clsx"><code>clsx</code></a> library to split rules onto separate lines:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">clsx</span><span style="color: #D8DEE9FF">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flex items-center justify-center gap-3 h-[40px]</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">rounded bg-purple-500 text-white text-semibold</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">[&amp;:not(:has(&gt;[data-slot=icon]:only-child))]:px-3</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">[&amp;:has(&gt;[data-slot=icon]:only-child)]:aspect-square</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">)</span></span>
<span class="line"></span></code></pre>
<p>This makes the styling more manageable while keeping the logic intact. Although, we'll admit, it can still get out of hand.</p>
<h2>Final thoughts</h2>
<p>We’ve used the <code>data-slot</code> attribute in several components to dynamically adjust spacing, layout, and behavior based on their children. The <code>:has()</code> selector has been a game-changer. Without it, this level of flexibility wouldn’t be possible.</p>
<p>This was a relatively simple use case, but Adam Wathan goes much deeper in his talk. He demonstrates how form components can adapt based on their children and even tackles nerve-racking dropdown component issues.</p>
<p>If you’re interested in learning more, we highly recommend watching <a href="https://www.youtube.com/watch?v=MrzrSFbxW7M&amp;ab_channel=Laravel">his talk</a>. Hopefully, this inspires you as much as it did for us!</p>
]]>
            </summary>
                                    <updated>2025-02-18T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Building a micro dependency container, because why not?]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/building-a-micro-container-because-why-not" />
            <id>https://flareapp.io/building-a-micro-container-because-why-not</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>We're currently completely rewriting the Flare backend client packages that run on our clients' servers. The original clients were written more than six years ago, and after more than a hundred releases, countless new features, and many bug fixes, it was time to review the packages.</p>
<p>In this blog post, I'm going to concentrate on the PHP agnostic package. Our Laravel package requires this package, which was written for non-PHP environments like WordPress, Symfony, CraftCMS, or any other PHP application/framework.</p>
<p>In Laravel applications and packages, we can use the almighty dependency container. A container can build PHP objects with dependencies on other objects, create objects based upon bindings provided by the developer, store singletons, and much more!</p>
<p>In our framework-agnostic package, we've refrained from using the Laravel Container since that would add an extra dependency on Laravel, which is weird in a framework-agnostic package. But with the rewrite, we found out that a single container that holds all the package dependencies is quite handy because it makes code a lot more readable and maintainable.</p>
<p>Therefore, let's add a container to the package that isn't the Laravel container. Other packages provide containers, and they work marvelously. But this also introduces an extra dependency, which we don't want.</p>
<p>As an extra requirement, since a lot of code is shared between the Laravel package and the framework-agnostic PHP package, it would be useful to share the bindings for building objects between the packages so that we only need to write them once.</p>
<p>That brings us back to the Laravel container ... software development is complicated. There's one extra feature about the Laravel container we haven't discussed: PSR-11. It is an abstraction written by the PHP-FIG and tries to create a standard interface so that framework components from different vendors can communicate with each other. Laravel's container is implementing the PSR-11 interface, which looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">interface</span><span style="color: #D8DEE9FF"> ContainerInterface</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">has</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>As you can see, it requires two methods to be implemented on a container:</p>
<ul>
<li><strong>get</strong> a way to retrieve (most of the time) an object from the container</li>
<li><strong>has</strong> a way to check if a binding exists in the container</li>
</ul>
<p>What if we built our own micro container implementing the <code>ContainerInterface</code> in our PHP agnostic package? Then, we would have a container in the PHP agnostic package, and since our container implements the same PSR interface, we could swap our container with Laravel's container in the Laravel package.</p>
<p>We could then create a ServiceProvider like this in the PHP agnostic package:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Contracts</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Container</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Container</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> IlluminateContainer</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">FlareClient</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Container</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">FlareProvider</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Container</span><span style="color: #ECEFF4">|</span><span style="color: #8FBCBB">IlluminateContainer</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">     </span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// register further bindings   </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>In a framework-agnostic project, you'll initialize Flare like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">your-api-key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Which would internally look like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">static</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">apiToken</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Container</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">instance</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">provider</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">FlareProvider</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">provider</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>In Laravel, we'll then have the following ServiceProvider:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">FlareServiceProvider</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ServiceProvider</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">FlareProvider</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">provider</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">provider</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">FlareProvider</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">provider</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Now, within both the Laravel and framework agnostic package, we'll be able to get dependencies from the container as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ViewExceptionMapper</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<h2>Binding things</h2>
<p>The PSR-11 interface has one problem: It defines whether a binding exists in a container and provides a way to retrieve it. What it doesn't provide is a way to add bindings.</p>
<p>This isn't a problem in the Laravel container since the container supports auto-wiring, which means that an object with dependencies can be automatically resolved by resolving its entire chain of dependencies.</p>
<p>In our homegrown micro container, we don't want to think about auto-wiring; while implementing such a feature isn't that complicated, it adds extra maintenance to a part of the package we didn't even want to build in the beginning.</p>
<p>As an alternative to auto wiring, we could define all our bindings manually in the code. While this initially might seem like a lot of work, since we're working with a package, there aren't that many dependencies, so it won't be that much work. Since the container will only be used by the package, we know that the set of dependencies won't grow unless we add them ourselves. This means we just need to remember to add the bindings, and we're done.</p>
<p>Lastly, auto-wiring is a cool feature, but it makes applications slower since a lot of reflection is required to determine how to construct objects and their dependencies. By manually defining bindings, we'll have the quickest container possible, which is great since we don't want our package to add too much of a performance penalty to our client's applications.</p>
<p>That leaves one question: How should manual bindings be defined? Since our ServiceProvider is shared with a Laravel and framework-agnostic container, let's do it the same way as Laravel.</p>
<p>A binding can be added as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ViewExceptionMapper</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ViewExceptionMapper</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ViewDataResolver</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>A default binding will be recreated every time it is resolved from the container. In some cases, we want to create a binding and store the object in the container so that the same object is returned the next time the binding needs to be created.</p>
<p>Basically, a singleton thus:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ConfigStore</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ConfigStore</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">config</span></span>
<span class="line"><span style="color: #ECEFF4">))</span></span>
<span class="line"></span></code></pre>
<h2>Building our container</h2>
<p>Let's take a look at the implementation of the container:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Psr</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Container</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ContainerInterface</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Container</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ContainerInterface</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * @template T</span></span>
<span class="line"><span style="color: #616E88">     *</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">class</span><span style="color: #616E88">-</span><span style="color: #81A1C1">string</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt;, Closure(): T&gt; $definitions</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">class</span><span style="color: #616E88">-</span><span style="color: #81A1C1">string</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt;, Closure(): T&gt; $singletons</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">class</span><span style="color: #616E88">-</span><span style="color: #81A1C1">string</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt;, T&gt; $initializedSingletons</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">definitions</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">singletons</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">initializedSingletons</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * @template T</span></span>
<span class="line"><span style="color: #616E88">     *</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">class</span><span style="color: #616E88">-</span><span style="color: #81A1C1">string</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt; $class</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">|</span><span style="color: #8FBCBB">Closure</span><span style="color: #ECEFF4">(</span><span style="color: #616E88">):</span><span style="color: #8FBCBB">T</span><span style="color: #616E88"> $</span><span style="color: #8FBCBB">builder</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">singleton</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">Closure</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">singletons</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * @template T</span></span>
<span class="line"><span style="color: #616E88">     *</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">class</span><span style="color: #616E88">-</span><span style="color: #81A1C1">string</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt; $class</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">|</span><span style="color: #8FBCBB">Closure</span><span style="color: #ECEFF4">(</span><span style="color: #616E88">):</span><span style="color: #8FBCBB">T</span><span style="color: #616E88"> $</span><span style="color: #8FBCBB">builder</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">Closure</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">definitions</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * @template T</span></span>
<span class="line"><span style="color: #616E88">     *</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">class</span><span style="color: #616E88">-</span><span style="color: #81A1C1">string</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt; $id</span></span>
<span class="line"><span style="color: #616E88">     *</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@return</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">T</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">array_key_exists</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">initializedSingletons</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">initializedSingletons</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">array_key_exists</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">singletons</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">initializedSingletons</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">singletons</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">]()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">array_key_exists</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">definitions</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">definitions</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">]()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ContainerEntryNotFoundException</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">has</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">array_key_exists</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">definitions</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">||</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">array_key_exists</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">singletons</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>That's probably the smallest PHP container out there, but it works!</p>
<p>Since a container is only resolved once per application, it is an excellent case for the singleton pattern. This allows us to access the container in places where passing the initially created container object is cumbersome.</p>
<p>We can quickly add support for a singleton like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Container</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ContainerInterface</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">static</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">instance</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">static</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">instance</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self::$</span><span style="color: #D8DEE9">instance</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * @template T</span></span>
<span class="line"><span style="color: #616E88">     *</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">class</span><span style="color: #616E88">-</span><span style="color: #81A1C1">string</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt;, Closure(): T&gt; $definitions</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">class</span><span style="color: #616E88">-</span><span style="color: #81A1C1">string</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt;, Closure(): T&gt; $singletons</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">class</span><span style="color: #616E88">-</span><span style="color: #81A1C1">string</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt;, T&gt; $initializedSingletons</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">definitions</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">singletons</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">initializedSingletons</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// The rest of the container code</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<h2>Define agnostic, extend in Laravel</h2>
<p>What if we created a binding in the framework-agnostic version of the package and then needed to redefine this binding in the Laravel package?</p>
<p>Well, that won't be a problem since the latest binding always takes precedence over the earlier defined bindings.</p>
<p>We can illustrate this with the following piece of code:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #8FBCBB">ViewExceptionMapper</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ViewExceptionMapper</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ViewDataResolver</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">bind</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #8FBCBB">ViewExceptionMapper</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">LaravelViewExceptionMapper</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ViewDataResolver</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ViewExceptionMapper</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// returns LaravelViewExceptionMapper</span></span>
<span class="line"></span></code></pre>
<p>This works great! But sometimes, we already have a binding in the framework-agnostic package and want to keep it as is but transform it just a bit to make it work with the Laravel package.</p>
<p>An example of such an object is the <code>Resource</code> object, a key-value store for the current resource (aka server, OS, network, etc.) on which the code is running.</p>
<p>In the framework agnostic package, we'll define the binding as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">singleton</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #8FBCBB">Resource</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Resource</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addAttribute</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">host.ip</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">gethostbyname</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">gethostname</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Then, in the Laravel version of the package, we also want to include the current version of Laravel the code is running on; this can be done by using a native function called <code>extend</code>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">container</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">extend</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #8FBCBB">Resource</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Resource</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">resource</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">resource</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addAttribute</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">laravel.version</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">version</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>The next time the Laravel container resolves the <code>Resource</code> class, not only <code>host.ip</code> but also <code>laravel.version</code> will be added. That's neat!</p>
<h2>In the end</h2>
<p>This micro container will appear in the next major version of the Flare PHP client. As Spatie, we have been considering turning this into a package (of course, we have 😅). This isn't the plan at the moment since it is just a tiny, non-complicated piece of code to make the package work.</p>
<p>But as a young Canadian pop idol once said, never say never.</p>
]]>
            </summary>
                                    <updated>2025-01-20T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Customizing error grouping]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/customizing-error-grouping" />
            <id>https://flareapp.io/customizing-error-grouping</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>For Flare, we've spent a lot of time optimizing our grouping algorithm. This algorithm groups occurrences of errors or exceptions together into one error.</p>
<p>Without such an algorithm, it would be impossible to use Flare since every time your application throws an exception or error, a new error is created in Flare, making oversight impossible.</p>
<p>We're quite proud of our algorithm since it works for 99% of the cases, but sometimes, in our support channel, we get the question if it would be possible to have a little more control over how errors are grouped.</p>
<p>We've implemented such a feature recently!</p>
<p>For example, let's say you're using an HTTP client to contact an external service, which returns a response with an error message when something is going wrong.</p>
<p>You're probably not implementing an HTTP client yourself, Guzzle or Laravel's HTTP client are perfect options for making HTTP requests without having to think about the rabbit hole of problems that might arise when sending HTTP requests.</p>
<p>The HTTP client package will make the request and throw an exception when something is wrong. The problem is that Flare's grouping algorithm will group all these exceptions even if the message differs.</p>
<p>That's for a reason. While exception messages might often look the same, as soon as a uuid, integer, email, or other user-specific information is present within the message, it would create a completely different error item within Flare (if we would group based upon the exception message).</p>
<p>When Flare processes an error, we examine its stack trace and a few other properties and intelligently group it.</p>
<p>Now, there are cases where we want to group by exception message, as mentioned above. This is possible starting today when using the latest version of the <a href="https://github.com/spatie/laravel-flare">laravel-flare</a> or <a href="https://github.com/spatie/flare-client-php">flare-client-php</a> package.</p>
<p>In the Laravel package, you'll find a new config value:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">overridden_groupings</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #616E88">//        Illuminate\Http\Client\ConnectionException::class =&gt; Spatie\FlareClient\Enums\OverriddenGrouping::ExceptionMessageAndClass,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">],</span></span>
<span class="line"></span></code></pre>
<p>In this array, you can define exception classes that should be grouped based on:</p>
<ul>
<li>ExceptionClass: group all exceptions with the same exception class together</li>
<li>ExceptionMessage: group all exceptions with the same message together</li>
<li>ExceptionClassAndMessage: group all exceptions with the same message and exception class together</li>
</ul>
<p>In the framework agnostic Flare client, you can add overrides as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">flare</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">overrideGrouping</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SomeExceptionClass</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OverriddenGrouping</span><span style="color: #81A1C1">::</span><span style="color: #D8DEE9FF">ExceptionMessageAndClass</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>We're constantly innovating Flare to improve the developer experience, and we'll share our biggest update within a few weeks. See you then!</p>
]]>
            </summary>
                                    <updated>2025-01-06T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Perfecting Flare: one annoyance at a time]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/perfecting-flare-one-annoyance-at-a-time" />
            <id>https://flareapp.io/perfecting-flare-one-annoyance-at-a-time</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Over the next few months, we're committed to fixing bugs - not just in your code, but in our app too.  We’ve got a bunch of improvements lined up to make Flare even better! Any issues you are having with Flare? Let us know on support@flareapp.io</p>
<h2>This week's special: better number formatting</h2>
<p>Something that's been bothering some of us for a while, is Flare's number formatting on the project overview. It feels like decoding the Matrix when looking at unformatted labels like <code>3249870 occurrences</code>.</p>
<p>So today, we've given our number formatting a much-needed makeover:</p>
<p><img src="https://content.spatie.be/assets/flare/perfecting-flare-one-annoyance-at-a-time/numbers.jpg" alt="Flare screenshot showing formatted numbers" /></p>
<p>Same data, just more pleasing to look at. Hopefully this small change makes your Flare experience better! Stay tuned as this is just the beginning of our quest to make Flare even better.</p>
]]>
            </summary>
                                    <updated>2024-10-18T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Browser extension errors begone!]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/browser-extension-error-noise-begone" />
            <id>https://flareapp.io/browser-extension-error-noise-begone</id>
            <author>
                <name><![CDATA[Sebastian]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Our team has received a lot of feedback about errors caused by browser extensions flooding projects. We have listened! In the latest version of our JavaScript client, errors triggered by browser extensions will not be reported anymore.</p>
<p>Our JavaScript client sends all errors to Flare. When the error is processing on our servers, we'll check if it's is likely caused by a browser extension. If it is, we won't report it and it won't count towards your monthly usage.</p>
<p>Update to our latest package version rid of those pesky errors, we'll take care of it from there!</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">npm install </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">@flareapp/js@1.1.0</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span></code></pre>
<p>If you <em>do</em> want to see errors caused by extensions, you can opt in to reporting them by configuring the client.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9">flare</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">light</span><span style="color: #D8DEE9FF">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">flare</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">configure</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #88C0D0">reportBrowserExtensionErrors</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>There's no way for a browser to tell us whether an error is triggered by an extension or not. We partially rely on magic (aka regex) to detect them. If you still notice a bunch of errors that shouldn't be there: let us know! We love data we can use to improve our product.</p>
]]>
            </summary>
                                    <updated>2024-10-10T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Using 1password for Laravel environment variables]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/using-1password-for-laravel-environment-variables" />
            <id>https://flareapp.io/using-1password-for-laravel-environment-variables</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Let's face it: secret management isn't the most thrilling part of development, but it's crucial. Even though it's very tempting to manage environment variables and secrets by <code>ssh</code>ing into a server and typing<code>vi .env</code>, better, and more secure alternatives are available. Let's look at some options, why we chose the 1Password CLI and how to set it up.</p>
<h2>Why should I care?</h2>
<p><img src="https://content.spatie.be/assets/flare/using-1password-for-laravel-environment-variables/secret-secrets.jpeg" alt="Secret secrets are no fun, secret secrets hurt someone" /></p>
<p>Secret management goes beyond just keeping things secure - it's also about access control and avoiding accidents. Not everyone needs (all) the keys to the kingdom. You might want to give your junior dev access to the staging environment, but keep the production credentials under stricter control. In corporate speak this is called &quot;implementing the principle of least privilege for effective risk mitigation in secret management&quot;, but we'll call it common sense.</p>
<h2>The right tool for the right job</h2>
<p>Before jumping right to the HashiCorp Vaults of this world, you might want to consider these more accessible options for your small to medium size Laravel application:</p>
<table>
<thead>
<tr>
<th>Product</th>
<th>Git Compatibility</th>
<th>Granular User Access</th>
<th>Encryption</th>
</tr>
</thead>
<tbody>
<tr>
<td>artisan env:encrypt</td>
<td>❌</td>
<td>❌</td>
<td>✔️</td>
</tr>
<tr>
<td>git-secret</td>
<td>❌</td>
<td>✔️</td>
<td>✔️</td>
</tr>
<tr>
<td>1Password CLI</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
</tr>
</tbody>
</table>
<p>As you can see, <code>env:encrypt</code> and <code>git-secret</code> are both effective tools for securely storing your environment variables. However, they save the encrypted secrets as binary files, which makes merging them with Git impossible. By using the 1Password CLI, we can manage <code>.env</code> files for all our application's environments without any of the downsides.</p>
<h2>1Password to rule them all</h2>
<p><img src="https://content.spatie.be/assets/flare/using-1password-for-laravel-environment-variables/precious.jpg" alt="My precious 1Password" /></p>
<p>For as long as I can remember, we’ve relied on 1Password to securely share passwords, secrets, and API keys with the Spatie team. It feels like a logical progression to also use their developer tools for integrating those very secrets into our deployments. Did I mention there's excellent <a href="https://plugins.jetbrains.com/plugin/19698-1password">JetBrains</a> and <a href="https://marketplace.visualstudio.com/items?itemName=1Password.op-vscode">VS Code</a> plugins as well, that make working with 1Password references in your config and .env files even easier?</p>
<h2>Generating <code>.env</code> files with 1Password</h2>
<p>1Password's CLI, <code>op</code>, has a couple of useful commands. We'll be using <code>op inject</code> to inject some secrets from 1Password entries into a template file.</p>
<p>The <code>.env.template</code> file:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">APP_KEY</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">op</span><span style="color: #81A1C1">:</span><span style="color: #616E88">//Flare/Environment/APP_KEY</span></span>
<span class="line"><span style="color: #616E88"># and other variables...</span></span>
<span class="line"></span></code></pre>
<p>Executing this command will output a new <code>.env</code> file with the 1Password references replaces with their actual values:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">op</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">inject -i .env.template -o .env</span></span>
<span class="line"></span></code></pre>
<p>The outputted <code>.env</code> file looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">APP_KEY</span><span style="color: #81A1C1">=</span><span style="color: #B48EAD">123</span><span style="color: #D8DEE9FF">abc</span></span>
<span class="line"><span style="color: #616E88"># and other variables...</span></span>
<span class="line"></span></code></pre>
<p>Simple, yet effective.</p>
<h2>One template, many environments</h2>
<p>Typically, you'll have different environment variables for your staging and production environment. Maybe you even want to share a set of local environment variables with your team to make local development easier.</p>
<p>For this, we'll create separate entries in our 1Password vault for each of these environments. Be sure to include the environment name in the entry title, as we'll use that to link it to the <code>APP_ENV</code>.</p>
<p><img src="https://content.spatie.be/assets/flare/using-1password-for-laravel-environment-variables/1pass-vault.jpg" alt="Screenshot of 1Password showing stagin-env and production-env entries" /></p>
<blockquote>
<p>[!TIP]<br />
Keep the set of variables consistent across environments. The <code>op</code> CLI will complain if you use a 1Password reference that doesn't exist in your 1Password entry.</p>
</blockquote>
<p>Next, you can use the <code>$APP_ENV</code> variable in your 1Password references to access the correct entries (or vaults):</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88"># The $APP_ENV will be replaced with &quot;staging&quot; or &quot;production&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">APP_KEY</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">op</span><span style="color: #81A1C1">:</span><span style="color: #616E88">//Flare/$APP_ENV-env/APP_KEY</span></span>
<span class="line"></span></code></pre>
<p>By setting the <code>APP_ENV</code> before running the <code>op inject</code> command, we can generate an <code>.env</code> file using a specific 1Password entry, for a specific environment:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">APP_ENV</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">staging op inject </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">i </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">env</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">template </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">o </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">env</span></span>
<span class="line"></span></code></pre>
<blockquote>
<p>[!WARNING]<br />
Instead of creating separate entries in 1Password for each environment, you should create different Vaults. This approach allows you to manage access to each environment's secrets separately.</p>
</blockquote>
<h2>Integrating 1Password in your deploy process</h2>
<p>Depending on your deployment process, there's two options to consider when integrating the 1Password CLI:</p>
<ul>
<li>generating the <code>.env</code> directly on your production server(s)</li>
<li>generating the <code>.env</code> file on your local machine (or CI) and copying it to the production server(s)</li>
</ul>
<p>We prefer the first option, this way we never have to store any secrets on your local machine or CI.</p>
<p>To access the 1Password vault on your production or staging servers, you'll need to <a href="https://developer.1password.com/docs/service-accounts/get-started/#create-a-service-account">create a 1Password service account</a>. The <code>op</code> CLI and service account also need to be configured on your production or staging servers. It's best to only configure this service account during the deployment process. This way the <code>op</code> CLI is useless for any bad actor that might gain direct access to the server.</p>
<p>In Laravel Envoy this looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">@setup</span></span>
<span class="line"><span style="color: #616E88">// Get the service account token using the local 1Password CLI</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">opServiceAccount</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">shell_exec</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">op read &quot;op://Flare/service-account/credential&quot;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">environment</span><span style="color: #D8DEE9FF"> ??</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">staging</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">@endsetup</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">@task</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">updateEnvironment</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">on</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">environment</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF"># Configure the service account only for this shell session</span></span>
<span class="line"><span style="color: #D8DEE9FF">export OP_SERVICE_ACCOUNT_TOKEN=</span><span style="color: #81A1C1">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">opServiceAccount</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">}}</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">// Set the APP_ENV based on the $environment and generate the .env</span></span>
<span class="line"><span style="color: #D8DEE9FF">APP_ENV=</span><span style="color: #81A1C1">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">environment</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">}}</span><span style="color: #D8DEE9FF"> op inject -i .env.1pass -o .env</span></span>
<span class="line"><span style="color: #81A1C1">@endtask</span></span>
<span class="line"></span></code></pre>
<p>In the <code>@setup</code> block, we're loading the service account credentials from our local <code>op</code> CLI. Once we're ready to generate the <code>.env</code> file on our remote server, the <code>updateEnvironment</code> task will configure the service account token and run the <code>op inject</code> command for the current environment.</p>
<p>If you're using GitHub actions to build or deploy your application, there's this <a href="https://github.com/1Password/install-cli-action">excellent GitHub action</a> to install, configure and use the <code>op</code> CLI in your GH actions.</p>
<h2>Wrapping up</h2>
<p>And there you have it - using the 1Password CLI for your Laravel <code>.env</code> files makes secret management less of a headache and more secure.</p>
]]>
            </summary>
                                    <updated>2024-09-30T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[When everything fails, you can always trust the Laravel rescue helper]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/when-everything-fails-you-can-always-trust-the-laravel-rescue-helper" />
            <id>https://flareapp.io/when-everything-fails-you-can-always-trust-the-laravel-rescue-helper</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Your application probably has a number of minor and more significant bugs. Some bugs will destroy your application, making it impossible to use, while others can be ignored.</p>
<p>The problem? When something goes wrong, we love to throw an exception; it is so easy:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Exception</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">This thing went wrong, I&#39;m done here it is up to the next person to handle this</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<p>But that exception doesn't tell you anything about the severity of what happened. It could be a TypeError where you used a string for a function argument while a complex object with multiple data properties is required. It could also be that you're sending an email notification to someone whose email address no longer exists, so the request failed.</p>
<p>In that first case, your application breaks, and that's probably best; we're trying to do something in our application with data that is not valid for the operation. While the second example shouldn't break the application, we'll want to remove that email address from our lists so that we stop sending to it next time.</p>
<h2>Try/Catch to the ... rescue</h2>
<p>Luckily, this problem can be quickly solved by using a try/catch statement:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">sendNotification</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">contents</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">EmailDoesNotExist</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">removeEmail</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Now, what if we want to track these <code>EmailDoesNotExist</code> exceptions? As said earlier, code always has bugs, and it could be that something is broken in this piece of code, and the email address is not being removed.</p>
<p>It would be handy to send these exceptions to an error tracker like Flare, which is the best error tracker for Laravel!</p>
<h2>Laravel error handling to the ... rescue</h2>
<p>We can change our try-catch statement like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">sendNotification</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">contents</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">EmailDoesNotExist</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">report</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">removeEmail</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The exception will be reported through Laravel's error handling system and eventually sent to a chosen error tracker, but your application will keep running like nothing happened.</p>
<p>A cool feature of Flare is that we mark these kinds of exceptions as handled with an extra label:</p>
<p><img src="https://content.spatie.be/assets/flare/handling-exceptions/screenshot-18.png" alt="A handled error" /></p>
<p>This label makes it easy to see if you should start panicking if an error is happening a bazillion times or if it is just business as usual.</p>
<p>But ... we're working with Laravel. Can we rewrite these lines of code to some of that polished beautiful Laravel code?</p>
<h2>Laravel rescue to the ...</h2>
<p>Laravel provides a rescue helper that mimics the try/catch statement we've written earlier. It works like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">rescue</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #88C0D0"> sendNotification</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">contents</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #88C0D0"> removeEmail</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>You've got to love the beauty of those four lines of code!</p>
<p>Do you want to decide if you're going to report the exception? You can add a third closure like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">rescue</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #88C0D0"> sendNotification</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">contents</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #88C0D0"> removeEmail</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">EmailDoesNotExist</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #88C0D0"> str_contains</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getMessage</span><span style="color: #ECEFF4">(),</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">@spatie.be</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<h2>Conclusion</h2>
<p>Laravel has many of these tiny helpers. The rescue helper is probably one of my favorites. They allow you to write more readable code even faster!</p>
]]>
            </summary>
                                    <updated>2024-08-23T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Check your Laravel Horizon failed jobs from Flare]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/check-your-laravel-horizon-failed-jobs-from-flare" />
            <id>https://flareapp.io/check-your-laravel-horizon-failed-jobs-from-flare</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Jobs in queues can fail—that's a reality! The good thing about jobs is that you can retry them because they have their own state.</p>
<p>In Flare, we've already been tracking failed jobs for quite some time; we can show you the job's initial state and where it went wrong.</p>
<p>We've added a new feature when you're using Laravel Horizon as a queue. Laravel Horizon provides a visual dashboard showing you all sorts of info about the queue, its failed jobs, and the ability to retry failed jobs. From now on, you can link your Flare project with the Laravel Horizon dashboard.</p>
<p>Every error triggered by a job in Laravel Horizon now links you to the failed job in the horizon dashboard. Allowing you to see even more info about the failed job and retry it:</p>
<p><img src="https://content.spatie.be/assets/flare/laravel-horizon-and-flare/screenshot.png" alt="screenshot" /></p>
<p>You can read more about configuring this feature in our <a href="/docs/integration/laravel-customizations/laravel-horizon">docs</a>.</p>
]]>
            </summary>
                                    <updated>2024-08-13T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Laravel's report helper: a must for error handling]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/laravels-report-helper-a-must-for-error-handling" />
            <id>https://flareapp.io/laravels-report-helper-a-must-for-error-handling</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>For any application, handling errors gracefully is crucial for maintaining a smooth user experience. While PHP's try-catch statements are great at catching known exceptions and preventing them from crashing your application, they also make exceptions go unnoticed. What if you also want to log and report an exception without bringing your application to a screeching halt? That's where Laravel's <code>report</code> helper steps into the spotlight.</p>
<h2>The Problem: Handling Known Failures</h2>
<p>Let's start with a common scenario: making an API call to an unreliable service. You know the request might fail, but you don't want it to bring down your entire application. Here's how you might handle something like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getData</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Response</span><span style="color: #ECEFF4">|</span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Http</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">https://crowdstrike.com/unreliable-api</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">RequestException</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	    </span><span style="color: #616E88">// We &quot;gracefully&quot; handle the error by returning null and not crashing</span></span>
<span class="line"><span style="color: #ECEFF4">	    </span><span style="color: #616E88">// but how do we let ourselves know this happened?</span></span>
<span class="line"><span style="color: #ECEFF4">	    </span><span style="color: #616E88">// Log the error? Send a notification to Slack? No, we&#39;ll use report!</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span></span>
<span class="line"><span style="color: #D8DEE9FF">	    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>In this example, we're catching the exception and returning <code>null</code> to prevent the application from crashing. We'll then handle the <code>null</code> value by informing the end user that something went wrong. But how do we let ourselves, the developers, know? How do you keep track of handled exceptions?</p>
<h2>Enter the <code>report</code> Helper</h2>
<p>Before we dive into the <code>report</code> helper, it's helpful to understand Laravel's default error handling behavior. By default, Laravel automatically logs unhandled errors and exceptions. If you're using an error tracking solution like Flare, these errors will also get sent to Flare. However, when you handle an exception in a try-catch block, Laravel doesn't automatically log or report it – that's where the <code>report</code> helper comes in.</p>
<p>The <code>report</code> helper is Laravel's solution for sending an error or exception you've handled to your logs and error tracking service, without halting the execution. It's perfect for situations where you want to catch and handle an error but still want to be notified about it.</p>
<p>Here's how it works:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">report(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #A3BE8C">);</span><span style="color: #ECEFF4">`</span></span>
<span class="line"></span></code></pre>
<p>The beauty of <code>report</code> is that it doesn't throw the exception or stop your code - it simply reports it and moves on.</p>
<p>In Flare, these reported exceptions appear like any other exception, distinguished by a &quot;handled&quot; label. This label indicates the exception was handled without crashing the application.</p>
<p><img src="https://content.spatie.be/assets/flare/laravels-report-helper-a-must-for-error-handling/handled-error.png" alt="A handled error report in Flare" /></p>
<h2>Beyond Exceptions: Reporting Custom Messages</h2>
<p>The <code>report</code> helper isn't limited to exceptions. You can also use it to report custom error messages:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">report</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">API call failed: unable to fetch user data</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Behind the scenes, Laravel will automatically create and report an exception for this message. This handy shortcut allows you to quickly provide more context about the edge-case, making debugging easier down the line.</p>
<h2>Conditional Reporting</h2>
<p>Finally, it's worth mentioning that, in true Laravel fashion, two additional helpers exists for conditionally <code>report</code>ing an exception: <code>report_if</code> and <code>report_unless</code>.</p>
<h2>A Real-World Example</h2>
<p>Let's revisit our initial example and see how we can use these helpers to implement a strategy of graceful degradation:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fetchUserData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">userId</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Http</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">example.com/api/users/</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">userId</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">successful</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">json</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">report_unless</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">notFound</span><span style="color: #ECEFF4">(),</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">API returned unexpected status: </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">response</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">status</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">HttpException</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">report</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>In this enhanced version:</p>
<ol>
<li>We're still catching and reporting any <code>RequestException</code> that might occur.</li>
<li>We're using <code>report_unless</code> to report unexpected status codes, but not 404s (which might be expected in some cases).</li>
<li>In both error cases, we're returning <code>null</code>, allowing the application to continue running without the user data.</li>
</ol>
<p>This approach allows your application to continue functioning even when parts of it fail, while ensuring you're always informed about these failures.</p>
<h2>Conclusion</h2>
<p>Laravel's <code>report</code> helper and its variants are invaluable for error handling. They allow us to keep track of handled errors and edge cases in our applications without disrupting the user experience. Errors are always inevitable but with Laravel's reporting helpers and Flare, you're well-equipped to manage them effectively.</p>
]]>
            </summary>
                                    <updated>2024-08-13T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Flare Microsoft Teams migration]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/flare-microsoft-teams-migration" />
            <id>https://flareapp.io/flare-microsoft-teams-migration</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Microsoft Teams is deprecating the old Office365 connection mechanism to post messages to team channels. This change affects all Flare users who rely on Flare notifications sent to Microsoft Teams.</p>
<p>To get these notifications working again. A new endpoint must be retrieved to which the notifications should be sent.</p>
<p>Since Microsoft stopped supporting the creation of webhooks using Office365 connections, all new Microsoft Teams connections within Flare will automatically use the new system.</p>
<p>We'll keep sending notifications to the old webhook URLs until the end of September 2024. After September, Microsoft will stop accepting these payloads, so update your webhook URL as quickly as possible!</p>
<p>Let's take a look at how to receive the new webhook URL!</p>
<h2>Getting a Microsoft Teams notifications webhook URL</h2>
<p>In Microsoft Teams, add or open the Workflows app (this is an application built by Microsoft to allow automation in Teams):</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot1.png" alt="" /></p>
<p>Next, create a &quot;New Flow&quot;</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot3.png" alt="" /></p>
<p>Microsoft Teams provides a few templates for typical flows, we 're going to create one from scratch:</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot4.png" alt="" /></p>
<p>Now a trigger for the flow should be selected; search for &quot;webhook&quot; and select &quot;When a Teams webhook request is received&quot;:</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot5.png" alt="" /></p>
<p>Make sure that &quot;Anyone&quot; (and thus Flare) can post to this Webhook:</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot6.png" alt="" /></p>
<p>Add a new step and, search for &quot;post&quot; and select &quot;Post card in chat or channel&quot;:</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot14.png" alt="" /></p>
<p>Configure the step with the following options:</p>
<ul>
<li><strong>Post as:</strong> Flow bot</li>
<li><strong>Post in:</strong> Channel</li>
<li><strong>Team:</strong> Select the team you want to post to</li>
<li><strong>Channel:</strong> Select the channel you want to post to</li>
</ul>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot7.png" alt="" /></p>
<p>For the Adaptive card entry, click on &quot;Add dynamic content&quot; and select the &quot;Expression&quot; tab:</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot8.png" alt="" /></p>
<p>Type in: <code>triggerBody()</code> and hit enter:</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot11.png" alt="" /></p>
<p>Hit the save button:</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot12.png" alt="" /></p>
<p>Then select the webhook URL from the &quot;When a Teams webhook request is received&quot; step:</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot13.png" alt="" /></p>
<p>Use this URL in the Flare dialog to receive Flare notifications in Microsoft Teams:</p>
<p><img src="https://content.spatie.be/assets/flare/microsoft-teams-migration/screenshot15.png" alt="" /></p>
]]>
            </summary>
                                    <updated>2024-07-22T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Handled exceptions]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/handled-exceptions" />
            <id>https://flareapp.io/handled-exceptions</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Exceptions will break your application, and your users will not be able to complete what they were doing in the first place. That's how I looked at exceptions a few years ago, and it even stopped me from using them for quite some time.</p>
<p>But this is not always the case. For example, in a Laravel application, the validator will throw an exception when your request validation fails. This exception does not break the application; it is cached by Laravel and converted into a response to the user indicating which fields contain invalid values.</p>
<p>Any exception can be caught like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">try</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// Do something that can fail</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">catch</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Throwable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// Handle what failed</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This is great because your application keeps running, and the user won't notice something crashes. The thing is, you probably want to know that something crashed.</p>
<p>An example of such a scenario here at Flare is generating <a href="https://flareapp.io/blog/flare-and-ignition-now-offer-ai-powered-solutions">AI solutions</a>. We use an external service for this and send some information about the exception to this service. In return, we receive a solution we can show you.</p>
<p>Sadly, we do not always receive a response. Sometimes, the AI can't figure out an answer in the time window we specified, and thus, an exception is thrown. Flare will keep working as is since we catch those exceptions, but what if we want to keep track of these responses? Or, even worse, what if something breaks and we stop generating AI solutions? Since we catch all exceptions, we wouldn't know.</p>
<p>The solution is pretty simple: instead of ignoring the exception, let's report it to Flare as such (in a Laravel context):</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">try</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// Contact our AI gods</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">catch</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Throwable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #88C0D0">report</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">t</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Everything will keep working as it was, but now the exception will be stored in Flare, and we can start monitoring it there!</p>
<p>Since the beginning of Flare, this has already been possible, but there hasn't been a visual difference between handled and unhandled exceptions until now!</p>
<p>When you install the latest version of ignition and report an exception, the exception will look like this in Flare:</p>
<p><img src="https://content.spatie.be/assets/flare/handling-exceptions/screenshot-18.png" alt="handled exception" /></p>
<h2>How did we do it</h2>
<p>In Laravel, there's only one way to report handled exceptions: the <code>report</code> method. The problem is that Laravel doesn't distinguish between manually reported exceptions and exceptions cached by the Laravel exception handler (exceptions that crash your application).</p>
<p>How can we determine whether the exception was handled or not? Backtracing!</p>
<p>If an exception is reported to Flare, it was triggered somewhere and followed a path through the application to the code that sends it to Flare. If we can somehow find that the <code>report</code> method was used within that path, the exception was handled!</p>
<p>That path is a backtrace; do not confuse it with the exception stacktrace, which will show which path leads to the exception. The backtrace will tell us what happened after the exception was thrown.</p>
<p>Creating a backtrace can be done using our <a href="https://github.com/spatie/backtrace">spatie/backtrace</a> package:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">frames</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Backtrace</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">limit</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">40</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">frames</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Now, we run over each frame and check the following:</p>
<ul>
<li>Is the frame method name equal to <code>report</code>?</li>
<li>Is the frame a class call and not a function call?</li>
<li>Are there any frames after this frame?</li>
</ul>
<p>In this case, it probably means we'll be in the <code>Illuminate\Foundation\Exceptions\Handler</code> class; we do not check if the frame class equals this since you can define your handler.</p>
<p>We now start checking the next frame, which is called the <code>Handler</code> class:</p>
<ul>
<li>Is the next frame a function call?</li>
<li>Is the next frame function call also named <code>report</code>?</li>
</ul>
<p>We'll mark the exception as handled if all these questions are confirmed for a frame within the backtrace.</p>
<h2>PHP integration</h2>
<p>We'll mark exceptions as handled automatically in Laravel. When using another framework or just plain PHP, you'll need to do this yourself as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Code that might throw an exception</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Exception</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">flare</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">reportHandled</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
]]>
            </summary>
                                    <updated>2024-07-05T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[WeakMaps a hidden gem in PHP]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/weakmaps-a-hidden-gem-in-php" />
            <id>https://flareapp.io/weakmaps-a-hidden-gem-in-php</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>We're working on a prominent new feature for Flare in the coming months. I hope to share more information about it in the coming weeks, but for now, we're going to keep it a secret.</p>
<p>We're rewriting the internals of some of the Flare packages for this feature, which led to a problem. It goes like this: we have an item that we store in an array with similar items like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88"> * @template T</span></span>
<span class="line"><span style="color: #616E88"> */</span></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Store</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt; $items</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">items</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">T</span><span style="color: #616E88"> $item</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">addItem</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">mixed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">[]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>There's nothing too fancy right now. The <code>Store</code> should keep a configured number of items, so we drop the first item added when we add a new item and the limit has been reached. We are always more interested in the items added at the process's end.</p>
<p>Such a thing can be easily implemented as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88"> * @template T</span></span>
<span class="line"><span style="color: #616E88"> */</span></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Store</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt; $items</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">items</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">maxEntries</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">200</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">T</span><span style="color: #616E88"> $item</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">addItem</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">mixed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">[]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">maxEntries</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">array_shift</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Now starts the complexity. We define a completely new structure called a <code>Node</code>. A <code>Node</code> can have children, which are again <code>Nodes</code>. We're basically creating a tree structure.</p>
<p>Here's the thing: our <code>Node</code> also has an array of items, the identical items we used in our <code>Store</code> earlier:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88"> * @template T</span></span>
<span class="line"><span style="color: #616E88"> */</span></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Node</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">Node</span><span style="color: #616E88">&gt; $children</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt; $items</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">readonly</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">children</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">items</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>We update our requirements so that when adding an item to the store, we'll also add that item to a node. To find the node, we use a <code>MagicNodeFinder</code> (something I just made up to keep this article a bit clearer). The <code>MagicNodeFinder</code> will just return a node where the item should be stored, but it is dependent on the time it runs, so it will return different nodes based on the time it has been called.</p>
<p>Our <code>Store</code> now looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88"> * @template T</span></span>
<span class="line"><span style="color: #616E88"> */</span></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Store</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">&gt; $items</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">items</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">maxEntries</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">200</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">T</span><span style="color: #616E88"> $item</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">addItem</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">mixed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">node</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MagicNodeFinder</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">[]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">node</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">[]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">maxEntries</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">array_shift</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>We're almost there. We need to update the item removal code so that items can be removed from the node when the item limit is reached.</p>
<p>Although this might look easy, it isn't since <code>MagicNodeFinder</code> cannot be trusted to return the correct node where the item is stored.</p>
<p>There's no way to know in which node the item is stored, and even if we knew the node, we'd have to traverse the tree every time we need to remove an item, resulting in wasted computing time.</p>
<h2>Introducing the WeakMap</h2>
<p>In PHP 8.0, WeakMaps were added to the language. Weakmaps are maps that hold objects as keys but without raising the reference count for that object. Thus, whenever an object stored in the WeakMap is garbage collected(it doesn't exist anymore because it went out of scope or was unset), it is also immediately removed from the map.</p>
<p>That all sounds difficult; let's take a look at an example. We refactor our <code>Node</code> class like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88"> * @template T</span></span>
<span class="line"><span style="color: #616E88"> */</span></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Node</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">string</span><span style="color: #616E88"> $id</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">Node</span><span style="color: #616E88">&gt; $children</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">WeakMap</span><span style="color: #616E88">&lt;</span><span style="color: #8FBCBB">T</span><span style="color: #616E88">, null&gt; $items</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">readonly</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">children</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">WeakMap</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">items</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">WeakMap</span><span style="color: #D8DEE9FF">()</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Let's add an item to our node:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Item</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Some info here</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">node</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Node</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">42</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">node</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"></span></code></pre>
<p>We count the number of items in the WeakMap:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">node</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 1</span></span>
<span class="line"></span></code></pre>
<p>If we garbage collect our item by unsetting it:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">unset</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>What happens to the WeakMap's count?</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">node</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// 0</span></span>
<span class="line"></span></code></pre>
<p>Cool! The item was automatically removed from the map by garbage collecting the item.</p>
<h2>Back to our <code>Store</code></h2>
<p>Let's see how we can use this <code>Weakmap</code> within our <code>Store</code>. The <code>addItem</code> method of our <code>Store</code> looks like this when using a <code>WeakMap</code> in <code>Node</code>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88"> * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">T</span><span style="color: #616E88"> $item</span></span>
<span class="line"><span style="color: #616E88"> */</span></span>
<span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">addItem</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">mixed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">node</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MagicNodeFinder</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">[]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">node</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">item</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">maxEntries</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">array_shift</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">items</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>By shifting the items array, the object will, due to scoping rules, be unset when the end of the method is reached. Thus, it will also be removed from the WeakMap, which is excellent! We're done; no extra changes are required here!</p>
<h2>Conclusions</h2>
<p>WeakMap was a feature added to PHP that I thought I would never use. Boy, was I wrong! It made our code a lot more performant and easier to read.</p>
<p>We have great things in the pipeline and would love to show them in the next months. See you soon!</p>
]]>
            </summary>
                                    <updated>2024-06-26T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Prioritizing exceptions]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/prioritizing-exceptions" />
            <id>https://flareapp.io/prioritizing-exceptions</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>If I'm not mistaken, it was Shakespeare who quoted:</p>
<blockquote>
<p>&quot;Verily, in the realm of errors, not all missteps bear equal weight, nor warrant the same swift hand. Some, mere trifles, may wait, while others, of grave import, demand urgent priority.&quot;</p>
</blockquote>
<p>That's not a real quote 😅 but the quote has a point: sometimes, you just want to have an overview of the errors in your application that need to be fixed now!</p>
<p>That's where prioritization comes into place, a new feature we've added to Flare. For each error, you can now decide which priority it needs:</p>
<p><img src="https://content.spatie.be/assets/flare/prioritizing-errors/screenshot-19.png" alt="select a priority" /></p>
<p>We've defined four levels of priority to select from:</p>
<ul>
<li>Info: not really an error, just some extra info from within your system</li>
<li>Low: something to pick up later on when you've got time for it</li>
<li>Medium: a bug that needs to be taken care off</li>
<li>High: drop everything you're working on; this issue should be resolved immediately!</li>
</ul>
<p>When a priority is changed, we'll keep track of it in the activity overview:</p>
<p><img src="https://content.spatie.be/assets/flare/prioritizing-errors/screenshot-20.png" alt="priority in the activity overview" /></p>
<p>Lastly, it is possible to filter errors in a project based on their priority:</p>
<p><img src="https://content.spatie.be/assets/flare/prioritizing-errors/screenshot-21.png" alt="filter based upon priority" /></p>
<p>Happy debugging!</p>
]]>
            </summary>
                                    <updated>2024-06-21T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Running Ignition on five years of Laravel]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/running-ignition-on-five-years-of-laravel" />
            <id>https://flareapp.io/running-ignition-on-five-years-of-laravel</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Here at Flare, we're trying to make the best Laravel error tracker possible, so we support as many Laravel versions as possible.</p>
<p>Flare and thus Ignition run on Laravel versions 6 (released in 2019) up to Laravel 11 (when writing the most recent version). Ignition is the default error page in Laravel 6, and we've completely rewritten it with Laravel 9 and added a new design.</p>
<p>The problem? When making changes to Flare or packages like <a href="https://github.com/spatie/backtrace">spatie/backtrace</a>, which powers the stack traces used by Ignition, how can we check that everything works when something changes?</p>
<p>Of course, we have continuous integration testing for this; the Ignition packages are tested with each commit and PR to ensure nothing breaks. Eventually, these packages will end up in a Laravel application for our Flare customers and everyone using Laravel. How can we make sure that they work with our install instructions? What if we add some new visual features to Ignition? How do we check for each Laravel version that the feature works? In these cases, we can wait for CI, but quickly checking something is just so handy!</p>
<h2>A nasty small bug</h2>
<p>We created a unique structure to set up Ignition and Flare in Laravel versions 6 to 11. The reason we did this was the following bug:</p>
<p><img src="https://content.spatie.be/assets/flare/ignition-for-five-years-of-laravel/screenshot-1.png" alt="A bug" /></p>
<p>Notice that the serializable closure is defined twice: once where it belongs in the code view and once as the filename, causing problems in Ignition. In this case, it is a small closure not causing too much trouble. But with bigger closures, Ignition becomes unusable, and Flare doesn't know what to do with the long filename.</p>
<p>This error can only be triggered using a serializable closure, a solution from Laravel that allows closures in jobs to be serialized. Simply said, the serializable closure package will keep a copy of the closure's code as a string and its state. Then, when unserialized, the code will be executed by including a stream of the string of the code (PHP magic at its best!).</p>
<p>Since we're not executing a file with code but a stream, there is no filename, and thus, the stream of code is used as a filename. Our code is floating in thin air between files. This is weird stuff, indeed.</p>
<p>The fix is to set the filename to <code>laravel-serializable-closure://function()</code>, and we also clean up the code example:</p>
<p><img src="https://content.spatie.be/assets/flare/ignition-for-five-years-of-laravel/screenshot0.png" alt="Fix of the bug" /></p>
<p>But how do we check if this works for all Laravel versions since this is a more visual bug?</p>
<h2>Building a test app(s) repository</h2>
<p>We've got an internal repository called <code>flare-test-apps</code> containing a Vue, React, PHP, and, of course, a Laravel app. We removed that last one and replaced it with 6 new Laravel apps, which require versions 6 through 11.</p>
<p><img src="https://content.spatie.be/assets/flare/ignition-for-five-years-of-laravel/screenshot-4.png" alt="A lot of laravel dirs" /></p>
<p>The next thing to do was to get them working simultaneously. Laravel 6 requires a minimum PHP version of 8.0, while Laravel 11 requires at least PHP 8.2. Luckily, we've got an excellent tool for this: Laravel Valet!</p>
<p>Laravel Valet is not only a server that can serve your local Laravel application but also can run CLI commands. The cool thing about Laravel Valet is that it can isolate sites, meaning that we can set a specific PHP version that will be used to serve the site and also run CLI commands.</p>
<p>To set this up, we loop using a bash script over all the <code>laravel-*</code> directories (where * is the Laravel version):</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">#!/bin/bash</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dir</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF"> laravel-</span><span style="color: #81A1C1">*;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">do</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[[</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-d</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$dir</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">${</span><span style="color: #D8DEE9">dir</span><span style="color: #81A1C1">#</span><span style="color: #D8DEE9">laravel-</span><span style="color: #81A1C1">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=~</span><span style="color: #D8DEE9FF"> ^</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">-9</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF">+$ </span><span style="color: #ECEFF4">]]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$dir</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">	      </span><span style="color: #D8DEE9">php_requirement</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">$(</span><span style="color: #88C0D0">grep</span><span style="color: #A3BE8C"> -o </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">&quot;php&quot;: &quot;[^&quot;]*</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C"> composer.json</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">php_versions</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">$(</span><span style="color: #88C0D0">echo</span><span style="color: #A3BE8C"> </span><span style="color: #D8DEE9">$php_requirement</span><span style="color: #A3BE8C"> </span><span style="color: #81A1C1">|</span><span style="color: #A3BE8C"> </span><span style="color: #88C0D0">tr</span><span style="color: #A3BE8C"> -dc </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">0-9.|\n</span><span style="color: #ECEFF4">&#39;)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[[</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">$php_versions</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">|</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">]]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">IFS</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">|</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">read</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-ra</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">versions </span><span style="color: #81A1C1">&lt;&lt;&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$php_versions</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">php_version</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">$(</span><span style="color: #88C0D0">printf</span><span style="color: #A3BE8C"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">%s\n</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">${</span><span style="color: #D8DEE9">versions</span><span style="color: #ECEFF4">[</span><span style="color: #A3BE8C">@</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C"> </span><span style="color: #81A1C1">|</span><span style="color: #A3BE8C"> </span><span style="color: #88C0D0">sort</span><span style="color: #A3BE8C"> -V </span><span style="color: #81A1C1">|</span><span style="color: #A3BE8C"> </span><span style="color: #88C0D0">tail</span><span style="color: #A3BE8C"> -n </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">else</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">php_version</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9">$php_versions</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">fi</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Linking </span><span style="color: #D8DEE9">$dir</span><span style="color: #A3BE8C"> with PHP version </span><span style="color: #D8DEE9">$php_version</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">valet</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">link </span><span style="color: #D8DEE9">$dir</span><span style="color: #A3BE8C">.flare-test-apps</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">isolated_sites</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">$(</span><span style="color: #88C0D0">valet</span><span style="color: #A3BE8C"> isolated</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$isolated_sites</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">grep</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-q</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$dir</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Isolating site </span><span style="color: #D8DEE9">$dir</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">valet</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">isolate php@</span><span style="color: #D8DEE9">$php_version</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">--site=</span><span style="color: #D8DEE9">$dir</span><span style="color: #A3BE8C">.flare-test-apps</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">fi</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">valet</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">composer update</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">fi</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">..</span></span>
<span class="line"><span style="color: #81A1C1">done</span></span>
<span class="line"></span></code></pre>
<p>I must admit, chatGPT was quite helpful in writing this BASH code. Let's go quickly over it:</p>
<ul>
<li>We loop over each laravel-* directory</li>
<li>We go into the directory</li>
<li>Check which php version was required by Composer
<ul>
<li>This could be <code>^7.4|^8.0</code>. We'll always take the highest version to reduce the amount of PHP versions required on our computers</li>
</ul>
</li>
<li>Then we'll tell Laravel valet that the subdomain laravel-*.flare-test-apps.test should serve the current directory by using the <code>valet link command</code></li>
<li>We'll check if the site is isolated at the moment by checking the <code>valet isolated</code> command if the current directory is isolated
<ul>
<li>If not, we'll isolate it using the PHP version found earlier and the <code>valet isolate</code> command</li>
</ul>
</li>
<li>We run <code>valet composer update</code>, which will run composer update in the directory</li>
<li>And we return back to the root dir so that we can visit other laravel-* directories</li>
</ul>
<p>From now on, we'll have a working version of all the required Laravel versions. You can check this by going to http://laravel-6.flare-test-apps.test or http://laravel-10.flare-test-apps.test.</p>
<h2>Adding common routes</h2>
<p>To check our serializable closure bug, we'll need a route and controller to trigger it. The code is simple, but copying and pasting it into every Laravel version directory would be stupid.</p>
<p>Since we at Spatie (the creators of Flare) are masters at creating packages, let us create yet another one! Albeit one that will never be published on Packagist, we call it laravel-shared. It will contain the route required for testing.</p>
<p>The package will live in the laravel-shared directory, and its composer file looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">name</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">spatie/laravel-shared</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">require</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">php</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">^7.4|^8.0</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">illuminate/contracts</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">^6.0|^7.0|^8.0|^9.0|^10.0|^11.0</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">laravel/serializable-closure</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">^1.3</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">autoload</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">psr-4</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">Spatie</span><span style="color: #EBCB8B">\\</span><span style="color: #8FBCBB">LaravelShared</span><span style="color: #EBCB8B">\\</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">src</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">extra</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">laravel</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">providers</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Spatie</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">LaravelShared</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">LaravelSharedServiceProvider</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">minimum-stability</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">stable</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Nothing too fancy, we register a <code>LaravelSharedServiceProvider</code> looking like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;?</span><span style="color: #D8DEE9FF">php</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">LaravelShared</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Route</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ServiceProvider</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">LaravelShared</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Controllers</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">FlareController</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">LaravelSharedServiceProvider</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ServiceProvider</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">boot</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Route</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">serializeable-closure</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #8FBCBB">FlareController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">serializeableClosure</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The service provider will register a route (like you would do in <code>routes/web.php</code>) to a controller where the bug will be triggered.</p>
<p>Within every Laravel application, we add a dependency for the <code>laravel-shared</code> package as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">require</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> : </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">php</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">^8.2</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">laravel/framework</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">^11.0</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">laravel/tinker</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">^2.9</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">spatie/laravel-shared</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">@dev</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">repositories</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> : </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">path</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #8FBCBB">URL</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">../laravel-shared</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"></span></code></pre>
<p>And that's it! But how can we now check this new route by typing in 6 URLs? We're developers, let's automate this by reusing the BASH script we wrote before:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">#!/bin/bash</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-z</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">No command provided, the following commands can be used:</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">  setup: Setup all Laravel applications</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">  open: Open a URL on all Laravel applications</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">exit</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #81A1C1">fi</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setup_laravel</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88"># Our previous code</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dir</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF"> laravel-</span><span style="color: #81A1C1">*;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">do</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[[</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-d</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$dir</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">${</span><span style="color: #D8DEE9">dir</span><span style="color: #81A1C1">#</span><span style="color: #D8DEE9">laravel-</span><span style="color: #81A1C1">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=~</span><span style="color: #D8DEE9FF"> ^</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">-9</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF">+$ </span><span style="color: #ECEFF4">]]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$dir</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Working in </span><span style="color: #D8DEE9">$dir</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">setup</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">setup_laravel</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">fi</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">open</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">open</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-a</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Safari</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">http://</span><span style="color: #D8DEE9">$dir</span><span style="color: #A3BE8C">.flare-test-apps.test/</span><span style="color: #D8DEE9">$2</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">fi</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">..</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">fi</span></span>
<span class="line"><span style="color: #81A1C1">done</span></span>
<span class="line"></span></code></pre>
<p>Now, when running our script like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">.</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">sh open serializable</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">closure</span></span>
<span class="line"></span></code></pre>
<p>Six browser tabs will open, each with a different Laravel version calling the route.</p>
<h2>Adding Flare support</h2>
<p>Wouldn't it be cool to test whether the Flare client works on each Laravel version by triggering <code>php artisan flare:test</code>?</p>
<p>To enable Flare in a Laravel application basically, three steps are required:</p>
<ul>
<li>Add a Flare key to the <code>flare.key</code> config entry (in regular applications, we'll check the <code>FLARE_KEY</code> environment variable for this)</li>
<li>Add a Flare logging channel to the <code>logging.channels</code> config entry</li>
<li>Update the <code>logging.channels.stack.channels</code> config entry also to include the logging channel we've added</li>
</ul>
<p>We can update the <code>LaravelSharedServiceProvider</code> in our <code>laravel-shared</code> package as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;?</span><span style="color: #D8DEE9FF">php</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">LaravelShared</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Route</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ServiceProvider</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">LaravelShared</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Controllers</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">FlareController</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">LaravelSharedServiceProvider</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ServiceProvider</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">boot</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Route</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">serializeable-closure</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #8FBCBB">FlareController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">serializeableClosure</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">flareKey</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">some-flare-api-key-here</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">flareKey</span><span style="color: #ECEFF4">){</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">set</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare.key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">flareKey</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">set</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">logging.channels.flare</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">driver</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">config</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">set</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">logging.channels.stack.channels</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">single</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This approach will work for the later versions of the Ignition client, but before Laravel 9, the Flare package (which will use the key to send a test) gets registered before <code>laravel-shared</code>. This means our update to the config will come too late, and the key will be set to <code>null</code>.</p>
<p>We've fixed this by adding the following lines to <code>app/bootstrap.php</code> in those versions:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">resolving</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Foundation</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Bootstrap</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">RegisterProviders</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">LaravelSharedServiceProvider</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>This line of code will cause Laravel to register the <code>LaravelSharedServiceProvider</code> before resolving all other service providers so that the key is set correctly.</p>
<p>Now we're ready to test the <code>php artisan flare:test</code> command on all the Laravel versions, but we're lazy developers. Let's update our bash script again so that we can run artisan commands:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">#!/bin/bash</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setup_laravel</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88"># Our previous code</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9">command</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$1</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #88C0D0">shift</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-z</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$command</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">No command provided, the following commands can be used:</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">  setup: Setup all Laravel applications</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">  composer: Run a composer command on all Laravel applications</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">  artisan: Run an artisan command on all Laravel applications</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">  open: Open a URL on all Laravel applications</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">exit</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #81A1C1">fi</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">dir</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF"> laravel-</span><span style="color: #81A1C1">*;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">do</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[[</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-d</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$dir</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">${</span><span style="color: #D8DEE9">dir</span><span style="color: #81A1C1">#</span><span style="color: #D8DEE9">laravel-</span><span style="color: #81A1C1">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=~</span><span style="color: #D8DEE9FF"> ^</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">-9</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF">+$ </span><span style="color: #ECEFF4">]]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$dir</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">||</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">exit</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Working in </span><span style="color: #D8DEE9">$dir</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$command</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">setup</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">setup_laravel</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">elif</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$command</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">composer</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">valet</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">composer </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$@</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">elif</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$command</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">artisan</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">valet</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">php artisan </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$@</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">elif</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9">$command</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">open</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">then</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">open</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">-a</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Safari</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">http://</span><span style="color: #D8DEE9">$dir</span><span style="color: #A3BE8C">.flare-test-apps.test/</span><span style="color: #D8DEE9">$@</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">else</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Invalid command: </span><span style="color: #D8DEE9">$command</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">fi</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">fi</span></span>
<span class="line"><span style="color: #81A1C1">done</span></span>
<span class="line"></span></code></pre>
<p>Now, it is possible to do the following:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">./laravel.sh</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">artisan flare:test</span></span>
<span class="line"></span></code></pre>
<p>Which will (thanks to Laravel Valet) run <code>php artisan flare:test</code> on all our Laravel versions.</p>
<h2>In the end</h2>
<p>We're constantly making changes to Flare to make it even better! This tiny bug fix, which will only occur on Laravel projects again, shows why Flare is the best error tracker for Laravel.</p>
]]>
            </summary>
                                    <updated>2024-06-10T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing Laravel Error Share]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-laravel-error-share" />
            <id>https://flareapp.io/introducing-laravel-error-share</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Earlier this month, Laravel changed the default error page from Ignition to their home-baked basic version.</p>
<p>Today, we are releasing a new package called <a href="https://github.com/spatie/laravel-error-share">spatie/laravel-error-share</a>, which brings one of Ignition's core features to the new error page: the ability to share errors.</p>
<p>In this blog post, we'd like to tell you all about it.</p>
<h2>Why you would share an error</h2>
<p>Imagine that you're working on a project locally and bump into an exception. You try to figure out the problem, but you're unable to find a solution. In that case, you might want to ask a colleague for help.</p>
<p>Instead of immediately starting screen sharing with your colleague (or letting your colleague roll their chair to your desk if you work in an office), it might be handy to just send a link to your colleague so that he or she can see that exception in detail.</p>
<h2>Using Laravel Error Share</h2>
<p>After you install <a href="https://github.com/spatie/laravel-error-share">the Laravel Error Share Page</a>, a new &quot;Share&quot; button will be displayed on the default Laravel error page. It looks like this.</p>
<p><img src="https://content.spatie.be/assets/share-button-1717745842.png" alt="" /></p>
<p>When you press that button, a dropdown menu will open, allowing you to choose which information to share.</p>
<p><img src="https://content.spatie.be/assets/share-contents.png" alt="" /></p>
<p>Of course, like the old Ignition page, we support dark mode as well.</p>
<p><img src="https://content.spatie.be/assets/dark@2x.png" alt="" /></p>
<p>When you press the create share button, all information about your exception will be sent to <a href="https://flareapp.io">Flare</a>, and a publicly accessible but hard-to-guess URL will be generated. On that URL, all information about your exception will be displayed.</p>
<p><img src="https://content.spatie.be/assets/flare@2x.png" alt="" /></p>
<p>When you scroll down you see even more information.</p>
<p><img src="https://content.spatie.be/assets/flare2@2x.png" alt="" /></p>
<p>You can send that URL to the person(s) you'd like to help you fix the exception. You could also add these URLs to GitHub issues or share them in Slack; they have no expiration date.</p>
<h2>Is this secure?</h2>
<p>When using Flare's sharing feature, you can choose what exception data and context to include in the shared error page. The following three sections are available for sharing and correspond to the following sections:</p>
<ul>
<li>Stack: Provides a detailed stack trace that shows the sequence of method calls leading to the error, including file paths and arguments.</li>
<li>Context: Offers relevant contextual information about the error, such as request payload, headers, and routing details.</li>
<li>Debug: Displays additional debugging information like dump output, SQL queries with bindings</li>
</ul>
<p>Generally, the data included in the Laravel error page and its shared errors is safe to share with colleagues.</p>
<p>However, as always, common sense applies. It is important to note that the data may contain sensitive information, such as database credentials, API keys, or other secrets. Therefore, it is recommended to review the data visible in the error page before sharing it with others. Be especially careful when dealing with production data locally.</p>
<h2>How to remove your exception</h2>
<p>To maintain privacy and control over shared error pages, Flare offers a simple method for removing shares. When sharing an error, an ownership cookie is automatically set in your browser. To delete the shared error, simply visit the shared error from the same browser and look for the &quot;Delete Share&quot; button.</p>
<p><img src="https://content.spatie.be/assets/delete@2x.png" alt="" /></p>
<p>In the rare event that the &quot;Delete Share&quot; button is not visible or you encounter any issues, you can contact <a href="https://flareapp.io">Flare</a> 's dedicated support team at support@flareapp.io and include the share URL you would like to have removed.</p>
<h2>In closing</h2>
<p>Sharing an error is very handy when you need some help from others to fix a local problem. Using our <a href="https://github.com/spatie/laravel-error-share">spatie/laravel-error-share</a> it is easy to make your exception available to others.</p>
<p>This service is provided for free by <a href="https://flareapp.io">Flare</a>, the best exception tracker for Laravel apps. Flare is built by all of us at Spatie, you might already know us by <a href="https://spatie.be/open-source">our extensive collection of open source packages</a>.</p>
]]>
            </summary>
                                    <updated>2024-06-06T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Creating issues is now easier than ever]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/creating-issues-is-now-easier-than-ever" />
            <id>https://flareapp.io/creating-issues-is-now-easier-than-ever</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>We love to add small features to Flare. This week's feature allows you to create issues way faster!</p>
<p>If you've integrated your Flare project with GitHub, GitLab, Jira, or Linear (yes, that list is growing rapidly), you can create issues for your errors directly within Flare.</p>
<p>As an added benefit, after you create the issue, it will be automatically linked with the error in Flare so you can keep track of it when using Flare.</p>
<p>In the past, we provided the option to write a title and a description for each issue. The only issue? Sometimes, you just want to add an issue quickly without the hassle of typing a lot.</p>
<p>We've got you covered! From now on, you can click on the &quot;Use details from error&quot; link to prefill some info about the error:</p>
<p><img src="https://content.spatie.be/assets/flare/creating-issues-is-now-easier/screenshot-15.png" alt="screenshot" /></p>
<p>Happy debugging!</p>
]]>
            </summary>
                                    <updated>2024-05-28T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Assigning a user to an error]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/assigning-a-user-to-an-error" />
            <id>https://flareapp.io/assigning-a-user-to-an-error</id>
            <author>
                <name><![CDATA[Niels]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>In popular project and source control systems, like GitHub, you can assign a person who should take care of a particular issue.</p>
<p>Now you can do this at Flare as well. In our latest release we've added support for assigning an error to a user of your team.</p>
<p>Let's dive into a quick demonstration of how you can achieve this:</p>
<video class="aspect-video" controls loop autoplay muted>
  <source src="https://content.spatie.be/assets/flare/assigning-a-user-to-an-error/assigning-an-error.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<h3>Filtering errors</h3>
<p>To navigate through errors, you can now filter them based on the assigned users. While typing, a list of users who have been assigned is shown, with the total number of assigned errors to this user.</p>
<video class="aspect-video" controls loop autoplay muted>
  <source src="https://content.spatie.be/assets/flare/assigning-a-user-to-an-error/filter-your-errors.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<p>Additionally, there's a filter available to display all unassigned errors.</p>
<video class="aspect-video" controls loop autoplay muted>
  <source src="https://content.spatie.be/assets/flare/assigning-a-user-to-an-error/filtering-unassigned.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<h3>Customize your settings</h3>
<p>Don't want to be notified when assigned or unassigned? You can tweak your notification setting too.</p>
<p><img src="https://content.spatie.be/assets/flare/assigning-a-user-to-an-error/settings.png" alt="" /></p>
]]>
            </summary>
                                    <updated>2024-05-14T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Rethinking deploys at Flare]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/rethinking-deploys-at-flare" />
            <id>https://flareapp.io/rethinking-deploys-at-flare</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Flare has long relied on locally running Laravel Envoy for our app deployments. However, as the application and infrastructure evolves, so do the challenges. This article documents some of our endeavors to come up with a more robust solution in 2024 while keeping our deploys as simple as possible.</p>
<h2>Our current deployment process</h2>
<p>At Flare, we've been long time fans of Laravel Envoy. This task runner allows you to use Blade syntax for running bash scripts locally or on your servers over SSH. This makes it a great tool for deploying your application. For Flare, we've used Envoy for years to perform atomic deployments, asynchronously SSH into our production servers, install and build dependencies and finally perform an <a href="https://www.netlify.com/blog/2021/02/23/terminology-explained-atomic-and-immutable-deploys/">atomic deployment</a>. This strategy is based on a <a href="https://serversforhackers.com/c/deploying-with-envoy-cast">blog post on Servers for Hackers</a> from back in 2015.</p>
<h2>A couple drawbacks</h2>
<p>Even though we've tweaked and changed a lot in the past 9 years, this approach has a couple fundamental drawbacks:</p>
<ul>
<li>because the deploy runs separately on every server, it's as slow as the slowest server</li>
<li>while usually not a problem, the results of composer install or the front-end build might end up being different on every server (for example due to different OS or cached dependencies)</li>
<li>disk space on every server needs to be monitored to avoid having a bunch of old releases taking up too much space</li>
<li>credentials for private NPM registries and Composer repositories need to be available on every server</li>
<li>every developer needs SSH access to every server</li>
<li>Envoy requires quite some additional &quot;boilerplate&quot; code to set-up zero-downtime, atomic deployments</li>
</ul>
<p>Some of these issues have been (partially) fixed by building the front-end assets locally and using Airdrop to distribute build to our servers, but ultimately we're looking for a faster, more resilient, more 2024-ready solution.</p>
<h2>Some requirements and ideas</h2>
<p>We've explored modern deployment solutions such as serverless and containerization in the past. However, for Flare's needs we prefer a more traditional deploy script that is transparent, simple to understand and easy to run. However, we will re-evaluate some key parts of our deploy script to address some of its aforementioned downsides.</p>
<h3>Use Deployer with recipes instead of Envoy</h3>
<p>Envoy is great for small websites with one or two servers. It has support for running tasks on multiple server, but deploying Flare from multiple branches to multiple servers with multiple environments (staging/production) required excessive boilerplate code.</p>
<p>Instead, we've started looking at <a href="https://deployer.org/">Deployer</a>. It's been around for a long time and provides similar features to Laravel Envoy. Additionally, it includes recipes for zero-downtime and atomic Laravel deployments out of the box. This eliminates the need for much of the boilerplate code in our current deployment script.</p>
<h3>Deploy on GitHub Actions instead of locally</h3>
<p>We've been running deployments from our local machines for years. Deploying straight from our local terminals has been pretty useful to keep the deploy process visible and the feedback loop short. While this worked great with a short deploy script and one or two developers, this is now becoming an issue. The biggest downside is that every developer needs to have access to all resources required to perform a deploy (e.g. SSH access, <code>.env</code> files, access to S3 for builds, etc).</p>
<p>Instead, we'll be switching to GitHub Actions as a CI/CD service. This way there's a single service that's responsible for accessing secrets and production services. This also enables us to further automate our release process.</p>
<h3>Centralised build and distribute using rsync</h3>
<p>One of the biggest annoyances in our current approach is that the deploy/build code is executed separately on every server. This means that if we have 5 servers, we'll run tasks like <code>composer install</code> and <code>yarn run production</code> once on every server. This doesn't just waste CPU cycles but also puts unnecessary pressure on production servers. The go-to solution would be to run build front-end assets on a separate CI/CD build server and copy the build to each server using something like Airdrop. However, we decided to take it one step further by preparing and building theapplication in a GitHub Actions runtime and using <a href="https://linux.die.net/man/1/rsync">rsync</a> to copy the <em>entire</em> application directory to every server, not just the required build files.</p>
<h3>Use Airdrop to only build while necessary</h3>
<p>We're already using Airdrop in our current deploy script and we love it. GitHub CI/CD pipelines offer numerous ways to cache build files but Airdrop takes it one step further by allowing us to cache the entire build and defining a set of rules to only invalidate this cached build if any of the relevant files changed. This would also be possible in a (lengthy?) bash script, but our goal is to simplify the deploy script, not complicate it further. It also integrates nicely with GitHub Action's cache store, making it possible to cache our front-end build right next to our deploy server, speeding things up quite a bit.</p>
<h2>Wrapping things up</h2>
<p>We have already started implementing and testing these changes and the first results are promising. Besides the benefits mentioned above, the deployment time has also reduced from 8-10 minutes on an M1 MacBook Pro to slightly over 3 minutes using the default GitHub Actions runner.</p>
<p>Finally, while working on this new deployment approach, we came across another excellent blogpost talking about atomic Laravel deploys using Deployer and GitHub Actions. If you're deploying to a single server, it's worth checking out &quot;<a href="https://christalks.dev/post/deploying-a-laravel-application-with-deployer-and-github-actions-718ece72">Deploying a Laravel Application with Deployer and GitHub Actions</a>&quot; by Chris Page. He goes into more detail about atomic releases and provides some practical code examples to get things set-up.</p>
]]>
            </summary>
                                    <updated>2024-04-24T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing: Linear integration]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-linear-integration" />
            <id>https://flareapp.io/introducing-linear-integration</id>
            <author>
                <name><![CDATA[Niels]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>After Github, Gitlab &amp; Jira, <a href="https://linear.app/integrations/flare">we now support Linear</a>!</p>
<p>You can create a Linear issue directly from a Flare error, automatically resolve and unresolve errors based on the Linear issue status or automatically complete a Linear issue when the error is resolved.</p>
<p>Let's go through the steps to set up Linear in Flare.</p>
<h3>Configuring Linear</h3>
<p>First, you must establish a connection between Flare and your Linear organization. This can be done in the team settings. Flare will ask for read-and-write access to your issues and comments on your Linear account.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-linear-integration/team-settings.png" alt="" /></p>
<p>After authorizing Linear, you can connect Flare projects with Linear teams in the project settings of your Flare project.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-linear-integration/project-settings.png" alt="" /></p>
<h3>Associating Flare errors with Linear issues</h3>
<p>When you've configured your Flare project to work with Linear, you'll get a new button on each error:</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-linear-integration/menu.png" alt="" /></p>
<p>Here, you can see all the Linear issues connected with this error and immediately create a new issue:</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-linear-integration/create-issue-2.png" alt="" /></p>
<p>Do you already have an issue within Linear? No problem! Just link the issue's URL to the error.</p>
<h3>Resolving Flare errors</h3>
<p>Flare will automatically resolve an error when an issue on Linear closes.</p>
<p>We will also unresolve an error on Flare whenever an issue is reopened on Linear.</p>
<h3>Configure the functionality</h3>
<p>Need to avoid some of these automations? You can configure how the integration should work in the project settings.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-linear-integration/project-settings-config.png" alt="" /></p>
<h2>In closing</h2>
<p>You can see other improvements we recently made <a href="https://flareapp.io/changelog">on our changelog</a>. Do you have an idea for improving Flare? <a href="https://github.com/spatie/flareapp.io-roadmap">Let us know</a>!</p>
]]>
            </summary>
                                    <updated>2024-04-09T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Ignition and Flare can now display Laravel 11's context]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/ignition-and-flare-can-now-display-laravel-11s-context" />
            <id>https://flareapp.io/ignition-and-flare-can-now-display-laravel-11s-context</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Laravel 11 recently introduced <a href="https://laravel.com/docs/11.x/context">a nice new feature called Context</a>. Both Ignition and Flare have been updated to collect and show all context.</p>
<h2>What is Context?</h2>
<p>Laravel's latest feature allows you to set some contextual values anywhere in your app. This allows you to store and exchange information across different requests, jobs, and commands in your application. This information is written alongside logs, aiding in understanding the sequence of events leading up to the log entry or error.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Context</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Context</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">add</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">url</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">url</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #8FBCBB">Context</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">add</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">trace_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Str</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">uuid</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toString</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>You can easily retrieve these values anywhere in your app.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Context</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Context</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">all</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// returns all context</span></span>
<span class="line"></span></code></pre>
<p>Now, this isn’t all that special. With the examples given above, you’d be right to think that Context is just some array-backed cache.</p>
<p>But the special thing that Context does is make all of this information available to any job dispatched in the request where context was set.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">dispatch</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #88C0D0"> </span><span style="color: #8FBCBB">MyJob</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// this job will receive the context</span></span>
<span class="line"></span></code></pre>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Contracts</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Queue</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ShouldQueue</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Context</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MyJob</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ShouldQueue</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// returns the context that was</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// set in the request that dispatched</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// this job</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">allContent</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Context</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">all</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// do some work</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// ...</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<h2>Context in Ignition and Flare</h2>
<p>Ignition, the error page we created and used in Laravel by default, now displays all set context.</p>
<p>Here’s what that looks like in Ignition.</p>
<p><img src="https://content.spatie.be/assets/cleanshot-2024-03-28-at-16.01.37@2x.jpg" alt="" /></p>
<p>Of course, Flare now collects and displays context as well. Here’s what that looks like when you have an exception in a request or job that holds context.</p>
<p><img src="https://content.spatie.be/assets/cleanshot-2024-03-29-at-15.35.10@2x.jpg" alt="" /></p>
]]>
            </summary>
                                    <updated>2024-04-01T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[How we stopped a DDoS attack at Laracon]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-we-stopped-a-ddos-attack-while-at-a-conference" />
            <id>https://flareapp.io/how-we-stopped-a-ddos-attack-while-at-a-conference</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>It is the first day of Laracon EU 2024. Flare was sponsoring the confererence for the first time and we were present with almost the whole Spatie team to promote it. The clock ticks 10:30, and inside the Bimhuis auditorium, Kévin Dunglas talk about how he built the awesome FrankenPHP server is almost over.</p>
<p>The doors of the auditorium open, and people come out towards us to talk about Flare or win a Playstation 5 / Lamborghini* at our booth. And suddenly, a message from Rias, who's still working at the office in Antwerp, pops up in Slack:</p>
<blockquote>
<p>@Freek @Alex @Ruben Flare down? #uptime looks like something DDoS 😬</p>
</blockquote>
<p>Oh no! We've been working towards this conference for weeks: preparing a sales talk (something we've never done; we're developers, not salespeople), crafting banners, picking t-shirts, and ordering a bunch of stickers.</p>
<p>This was our moment, the moment for Flare to shine, and it was down. With Alex, I rushed behind our promo banner, grabbed our MacBooks, and quickly formed a team with Rias at the office and Mathias from the befriend <a href="https://ohdear.app">Oh Dear</a> booth next to us. We started investigating the problem.</p>
<p><img src="https://content.spatie.be/assets/screenshot-2024-02-07-at-13.07.14-(1).jpg" alt="Fixing Flare" /></p>
<p><em>Taylor and Freek talking about Flare, while Ruben and Mathias try to fix Flare</em></p>
<p>At that moment, we received a 200x increase in requests compared to a regular day. WTF. Those requests were all sent to our servers, which spun up PHP-FPM processes as quickly as possible to handle the requests. Unfortunately, our server couldn't handle such a load, resulting in downtime.</p>
<p>The first idea that popped into our heads was to spin up more web servers or scale our existing servers so that they could manage the load. However, we quickly realized that it was impossible to manage a load like this by adding extra servers.</p>
<p>While the rest of our team was talking to the conference attendees, that Flare is THE Laravel error tracker, with a small but dedicated team always listening to our clients to create the best error tracker ever. That error tracker was now down for half an hour.</p>
<p>Then it hit us: CloudFlare! We had CloudFlare in front of Flare all the time. Cloudflare has an &quot;I'm under attack&quot; switch, which basically adds a JavaScript challenge to each request coming into Flare. You, as a human, can skip this request by clicking the famous &quot;I'm not a robot&quot; checkbox. Robots are less lucky and can't surpass the challenge.</p>
<p>We hit that &quot;I'm under attack switch,&quot; and WOW! Immediately, we saw a reduction in the number of requests hitting the Flare servers. CloudFlare was blocking more than 99% of the requests at that time. We restarted our FPM services, and Flare was back online!</p>
<p>Now was the time to relax ... we were thinking. The great thing about the &quot;I'm under attack&quot; switch is that it immediately stops all non-human requests to a server. The problem with the switch is that it stops all non-human requests to a server. Flare is an error-tracking service. Many people send us errors from their servers. These requests, albeit non-human, are valid requests. By stopping the DDoS, we stopped the ingestion of errors.</p>
<p>Flare exists of two groups of servers, first we have our web servers they allow you to browse to the Flare application. The attack was targeted at these servers, specifically the login endpoint.</p>
<p>Another set of servers are the ingest servers. Ignition and the Flare client send errors from our clients' applications to these servers. There's no human interaction, and these servers were not under attack.</p>
<p>The ingestion servers kept working during the attack because a DDoS attack cannot attack them because the routes are protected by Cloudflare workers (kinda like Lambda functions). The workers check if a request has a valid API key and if the team connected to that API key has enough quota to send us its errors. When an API key does not exist, is invalid, or exceeds its quota, the request will be dropped.</p>
<p>That's why we kept processing errors sent to us during the attack. Our ingestion servers purred like kittens, and they didn't notice anything going on at the web servers.</p>
<p>Back to CloudFlare, the &quot;I'm under attack&quot; mode stopped the DDoS attack on our web servers, but it also stopped legitimate requests made to our ingestion servers. Luckily, the CloudFlare challenge can be configured, and after a few minutes, we managed to exclude it from our ingestion servers.</p>
<p>For the next two hours, the attacker kept sending the same ridiculous amount of requests to our servers. But Flare didn't break a sweat—we were back online!</p>
<p>We found an email addressed to us in our support system. The attacker wanted a certain amount of ransom money. After the payment, he would stop the attack. A few Google searches later, we found other small companies on Twitter (ehm X) that the same attacker targeted. We're glad we didn't pay.</p>
<p>That was one hell of a ride, our first DDoS attack. To let things run smoother the next time, we've updated our internal documentation so that the next time a DDoS attack happens, everyone on our team can configure CloudFlare to stop blocking the correct requests.</p>
<p>To all the other small SAAS companies like us, ensure you have a service like CloudFlare between your servers and the web. It is extremely helpful in these kinds of situations.</p>
<p>* It was a toy Lamborghini, still cool, though 😎</p>
]]>
            </summary>
                                    <updated>2024-03-27T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Migrating from MySQL to Postgres using Laravel's query builder]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/migrating-from-mysql-to-postgres-using-laravels-query-builder" />
            <id>https://flareapp.io/migrating-from-mysql-to-postgres-using-laravels-query-builder</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Migrating from MySQL to Postgres is not an easy feat in general; there are numerous packages &amp; scripts to accomplish this, like <a href="https://pgloader.io/">Pgloader</a> and <a href="https://pgloader.io/">Nmig</a>.</p>
<p>For us, the best way to do this was using Laravel's query builder, since it is agnostic to the database layer and allows us to transform any data as needed in a language we know (PHP) and a framework we know and love.</p>
<h2>Why?</h2>
<p>First off, a small note on why we moved from MySQL to Postgres since that's probably on everyone's mind reading this.</p>
<p>The main reason is that we were having trouble searching full text to work properly in MySQL for our use case. Our tests and subsequent migration to Postgres made our full-text search more reliable and faster.</p>
<p>We could use a different service or technology for our searching, but in the spirit of <a href="https://mcfunley.com/choose-boring-technology">boring technology</a>, Postgres does its job perfectly.</p>
<p>Looking toward the future, Postgres also offers many exciting opportunities thanks to its extensibility, for example, extensions like <a href="https://www.timescale.com/">Timescale</a>.</p>
<h2>Preparing our database config</h2>
<p>Our source database is MySQL; since this is our default, you could keep the database configuration as-is for the MySQL connection. However, we prefer to create a new one called <code>source</code>.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">source</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">driver</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">mysql</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">url</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">SOURCE_DATABASE_URL</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">host</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">SOURCE_MYSQL_DB_HOST</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">127.0.0.1</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">port</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">SOURCE_MYSQL_DB_PORT</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">3306</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">database</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">SOURCE_MYSQL_DB_DATABASE</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">forge</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">username</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">SOURCE_MYSQL_DB_USERNAME</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">forge</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">SOURCE_MYSQL_DB_PASSWORD</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">...</span></span>
<span class="line"><span style="color: #ECEFF4">],</span></span>
<span class="line"></span></code></pre>
<p>Make sure to configure the necessary environment keys starting with <code>SOURCE_</code></p>
<p>The next thing to do is to configure the <code>destination</code> connection in the <code>config/database.php</code> file to connect to the target database using the <code>pgsql</code> driver:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">destination</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">driver</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">pgsql</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">url</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DATABASE_URL</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">host</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DESTINATION_DB_HOST</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">127.0.0.1</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">port</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DESTINATION_DB_PORT</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">5432</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">database</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DESTINATION_DB_DATABASE</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">forge</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">username</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DESTINATION_DB_USERNAME</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">forge</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">DESTINATION_DB_PASSWORD</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">...</span></span>
<span class="line"><span style="color: #ECEFF4">],</span></span>
<span class="line"></span></code></pre>
<h2>Creating the tables in Postgres</h2>
<p>Laravel makes this extremely straightforward for us here, as you can run the migrations against the target database and have all the tables created using the correct field types.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">php</span><span style="color: #D8DEE9FF"> </span><span style="color: #A3BE8C">artisan migrate --database=destination</span></span>
<span class="line"></span></code></pre>
<h2>Creating a command to backfill the data</h2>
<p>Since we're migrating tables with a lot of data, we need a way to sync the majority of the data before the migration. This way, we can minimize the downtime needed to execute a last-minute sync and switch over the database connection.</p>
<p>Laravel's query builder makes retrieving the data from the source MySQL database and inserting it into the destination Postgres database straightforward, as the query builder will change its schema language depending on the database driver.</p>
<p>Below you can find a simplified command from the one we used that retrieves the necessary data in a performant way and inserts it into the target database.</p>
<p>The command accepts two parameters: the <code>table</code> it needs to migrate and a <code>--from</code> parameter that lets you skip until a certain id. This makes the command easily restartable without needing to loop over thousands of rows that have been processed already.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MigrateDatabaseCommand</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Command</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">signature</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">app:migrate-database {table} {--from=}</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">argument</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">table</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getOutput</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Importing table </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">table</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">total</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DB</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">connection</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">source</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">table</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">when</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">option</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">from</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">&gt;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">option</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">from</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)))</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getOutput</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">progressStart</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">total</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">DB</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">connection</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">source</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">table</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">when</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">option</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">from</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">&gt;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">option</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">from</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)))</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">select</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">chunkById</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1_000</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sourceRows</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sourceIds</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Arr</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">pluck</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sourceRows</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">destinationRows</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DB</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">connection</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">destination</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">table</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">whereIn</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sourceIds</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">select</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toArray</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">missingIds</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">array_diff</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">sourceIds</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #8FBCBB">Arr</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">pluck</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">destinationRows</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">missingIds</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">missing</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DB</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">connection</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">source</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">table</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">whereIn</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">missingIds</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #8FBCBB">DB</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">connection</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">destination</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">table</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">insert</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">missing</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getOutput</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">progressAdvance</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">option</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">chunk</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getOutput</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">progressFinish</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This command loops over every ID inside the source database and checks which ones are already present in the destination database, if there are any missing IDs, the rows will be inserted into the destination database.</p>
<p>These extra checks ensure the command can be re-run at any time, and only missing rows will be fetched and inserted into the new database.</p>
<h2>A couple of &quot;gotcha's&quot;</h2>
<h3>Rows that are updated or deleted between syncs</h3>
<p>We have a unique situation where most tables could be wiped and imported into the destination database in seconds. These tables were, in this case, synced at the time of migration.</p>
<p>The other tables, with millions of rows, are rows inside an &quot;append only&quot; type table that is not updated and is cleaned up after a time period. In this example, we do not have any checks on rows that would need updating or exist in the destination but no longer in the source, for example, when a user deletes some rows.</p>
<p>If your project needs this kind of functionality, then be sure to build it in your command.</p>
<h3>Column lengths</h3>
<p>Make sure your column lengths are defined large enough in Postgres for the existing (and future) data you have. MySQL does not complain and truncates your data. However, Postgres is more strict in this regard and throws an exception when inserting too long data for a column.</p>
<h3>Update any custom queries</h3>
<p>If you're not using the Laravel query builder for some queries, this usually means you're using some functionality specific to the database driver. Make sure to update these queries!</p>
]]>
            </summary>
                                    <updated>2024-03-06T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing our new suite of JavaScript packages]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-our-new-suite-of-javascript-packages" />
            <id>https://flareapp.io/introducing-our-new-suite-of-javascript-packages</id>
            <author>
                <name><![CDATA[Sebastian]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>We just released our new suite of JavaScript packages. This <a href="https://github.com/spatie/flare-client-js">repository</a> contains the updated packages for our JavaScript, React, Vue, and Vite plugins.</p>
<h2>What's new?</h2>
<p>All packages have been updated so they can also run in a Node.js environment as they don't depend on browser APIs anymore. This is useful if you want to report errors from a Node server or during server side rendering. In addition, the default reporting endpoint has changed from <code>flareapp.io/api/reports</code> to <code>reporting.flareapp.io/api/reports</code>. Our reporting endpoint now runs through CloudFlare. The old one still works, but redirects to the new one. After upgrading, error notifications will land in your inbox approximately 200ms faster than before!</p>
<h2>Upgrading</h2>
<p>To upgrade, remove the previous <code>@flareapp/*</code> packages from your project's <code>package.json</code>. Then, install <code>@flareapp/js</code>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">npm i </span><span style="color: #81A1C1">@</span><span style="color: #D8DEE9FF">flareapp</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">js </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">save</span></span>
<span class="line"></span></code></pre>
<p>If you're using React or Vue, install one of our framework plugins too:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">npm i </span><span style="color: #81A1C1">@</span><span style="color: #D8DEE9FF">flareapp</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">react </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">save</span></span>
<span class="line"><span style="color: #D8DEE9FF">npm i </span><span style="color: #81A1C1">@</span><span style="color: #D8DEE9FF">flareapp</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">vue </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">save</span></span>
<span class="line"></span></code></pre>
<p>If you're building assets with Vite, install our Vite plugin to upload your sourcemaps to Flare:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">npm i </span><span style="color: #81A1C1">@</span><span style="color: #D8DEE9FF">flareapp</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">vite </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">save</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">dev</span></span>
<span class="line"></span></code></pre>
<p>All packages have the same API as their predecessors/ For detailed installation instructions, please refer to our <a href="http://flareapp.io/docs/integration/javascript-error-tracking/framework-integrations">documentation</a>.</p>
<p>With this new foundation, we're excited to add more features and framework integrations in the future. Stay tuned!</p>
]]>
            </summary>
                                    <updated>2024-02-20T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Enhancing error reporting with custom context and glows]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/enhancing-error-reporting-with-custom-context-and-glows" />
            <id>https://flareapp.io/enhancing-error-reporting-with-custom-context-and-glows</id>
            <author>
                <name><![CDATA[Zuzana]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When it comes to debugging errors in your Laravel applications, Flare already collects several Laravel and user-specific information to send along with the error. However, an additional layer of customization is available that can provide even deeper insights into your application's state when the error or the exception arises: custom context.</p>
<h2>Adding custom context</h2>
<p>Implementing custom context in Flare is relatively straightforward. You can set context items using the provided Flare facade within your Laravel application in the service provider or an event:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">LaravelIgnition</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">context</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Tenant</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">My-Tenant-Identifier</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Once set, the next time an exception happens, the custom value will be in the error's context tab on the Flare dashboard.</p>
<h2>Adding a group of context items</h2>
<p>But what if you have multiple items of custom context to include? Flare allows you to group related context items under a common label. Here's how you can create custom context groups:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">LaravelIgnition</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">group</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Custom information</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">value</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">another key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">another value</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<h2>Adding a glow</h2>
<p>While adding context is already very useful, we can take error reporting even further. To understand what happened in your application before each error, it can be helpful to log short statements, also known as <code>glows</code>. Glows would be especially useful to log events that might happen multiple times during the request, for example, when calling a payment provider API.</p>
<p>You can set up glows in your Laravel's service provider:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">LaravelIgnition</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">FlareClient</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Enums</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">MessageLevels</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">glow</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">This is a message from glow!</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MessageLevels</span><span style="color: #81A1C1">::</span><span style="color: #D8DEE9FF">DEBUG</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">func_get_args</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"></span></code></pre>
<p>Once enabled, glows can be found in chronological order in the Debug tab of the error on the Flare dashboard.</p>
<p>Flare already excels at capturing essential data for error diagnosis, but leveraging custom context and glows allows you to debug the errors even better. If that sounds like something you want to explore, you can try Flare for free! New customers get a 10-day trial, and no credit card is required to sign up. Check out our <a href="https://flareapp.io/docs">documentation</a> for more information.</p>
]]>
            </summary>
                                    <updated>2024-02-18T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Flare ❤️ GitLab]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/flare-gitlab" />
            <id>https://flareapp.io/flare-gitlab</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Just last week, we shared the exciting news about the <a href="https://flareapp.io/blog/introducing-jira-integration-with-flare">Flare-Jira integration</a>, and today, we're delighted to introduce yet another powerful integration: GitLab.</p>
<h3>Configuring GitLab</h3>
<p>GitLab stands out for its remarkable self-hosting capabilities, allowing you to set up your instance for complete control. Alternatively, you can seamlessly leverage the cloud version by registering on <a href="https://gitlab.com/">gitlab.com</a>. The best part? Flare effortlessly supports both options, granting you the freedom to choose the setup that suits your preferences:</p>
<p><img src="https://content.spatie.be/assets/flare/flare-loves-gitlab/gitlab-choose-instance.png" alt="" /></p>
<p>For those opting for the cloud version, the process is as straightforward as authorizing the Flare application. </p>
<p>However, if you're venturing into self-hosting, worry not – we've prepared a comprehensive wizard to walk you through the process of creating a Flare app within your GitLab instance:</p>
<p><img src="https://content.spatie.be/assets/flare/flare-loves-gitlab/gitlab-selfhosted-install-1.jpg" alt="" /></p>
<p>Once successfully connected, you can fine-tune which Flare projects engage with specific GitLab projects. Tailor settings to your liking, deciding what should occur with a linked issue when an error is resolved or is unresolved:</p>
<p><img src="https://content.spatie.be/assets/flare/flare-loves-gitlab/gitlab-project-settings.png" alt="" /></p>
<h3>Creating and Managing Issues</h3>
<p>With Flare configured to sync seamlessly with GitLab, a new button emerges on every error within the project, revealing linked issues:</p>
<p><img src="https://content.spatie.be/assets/flare/flare-loves-gitlab/gitlab-linked-issues.png" alt="" /></p>
<p>From this panel, you can quickly create a new issue:</p>
<p><img src="https://content.spatie.be/assets/flare/flare-loves-gitlab/gitlab-create-issue.png" alt="" /></p>
<p>Or link an existing issue using its URL:</p>
<p><img src="https://content.spatie.be/assets/flare/flare-loves-gitlab/gitlab-link-url.png" alt="" /></p>
<h3>Two-way sync?</h3>
<p>Our GitHub and Jira integration have a two-way sync, which means that if something happens on GitHub/Jira, Flare will react. For example, if you close an issue on GitHub with a Flare error linked to it, then Flare can automatically resolve the error for you.</p>
<p>Such functionality does not exist (yet?) for GitLab. We'll discuss it in a blog post later this month; stay tuned!</p>
<h3>A Year of Integrations</h3>
<p>That's it for now. This year, we've released two new integrations: Jira &amp; GitLab, and we've completely rewritten our GitHub integration. </p>
<p>Are there any other integrations you love to see in Flare? Let us know, and your integration of choice might be our next project!</p>
]]>
            </summary>
                                    <updated>2023-12-06T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[How we built our GitLab integration]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-we-built-our-gitlab-integration" />
            <id>https://flareapp.io/how-we-built-our-gitlab-integration</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>A few days ago, we added a GitLab integration. In this blog post, we'll talk about the road to that integration and our struggles.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-loves-gitlab/gitlab-linked-issues.png" alt="" /></p>
<p>Frequent readers of our blog might have read <a href="https://flareapp.io/blog/building-a-flexible-yet-abstract-external-integrations-structure">Building a flexible yet abstract external integrations Structure</a>. In that technical blog post, we describe how we revamped our internal structures to allow us creating integrations like GitHub, Jira, and GitLab in days rather than weeks. If you haven't read it, be sure to read it first.</p>
<p>We started building these new structures with the Jira integration and used them again when we converted our existing GitHub integration. To ensure these structures were as flexible as we wanted them to be. It was required to battle-test them!</p>
<p>Could we set up a new Flare integration from scratch in two days? That's where GitLab comes into the picture. Could we add a GitLab integration like we did on GitHub and Jira without breaking a sweat?</p>
<p>At Spatie, we're using GitHub. We had no experience with GitLab, but they kinda follow the same conventions as GitHub. Issues are issues, and a repository is called a project, a team is a group, and, oh yeah, you can nest groups.</p>
<p>The most complex part of the GitLab integration was that GitLab exists in two flavors, like GitHub, you can use the cloud version, where you create an account, and you're ready to go. GitLab also offers the ability to be self-hosted; before you can create an account, you'll need to set up a GitLab instance on your server, and after that, you can create an account.</p>
<p>This self-hosted option added extra challenges for us. Because we cannot, like with GitHub and Jira, create an application for Flare on GitLab with which we can connect. Instead, a user should create its own application with which Flare can connect when using a self-hosted GitLab instance.</p>
<p>We provide an application for the cloud version, which makes connecting Flare with the cloud version of GitLab as complex as clicking one button, which is nice.</p>
<p>When installing a new GitLab connection into Flare, you will be asked to use the Cloud or self-hosted version, allowing you to choose your personal favorite.</p>
<h2>Implementing</h2>
<p>We spend most of our time finding out how GitLab works, reading the GitLab API documentation, and writing front-end code.</p>
<p>Implementing the backend code was straightforward; the integration structures we discussed earlier worked like a charm. Let's go through a few of them:</p>
<h3>IntegrationIssueData</h3>
<p>The <code>IntegrationIssueData</code> object which represents an individual issue :</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitLabIntegrationIssueData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">IntegrationIssueData</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">number</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">title</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">description</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitLabIssueState</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">state</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitLabIssueType</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">type</span><span style="color: #ECEFF4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getId</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">static</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">createFromApiPayload</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            id: </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">iid</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            number: </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">iid</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            title: </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">title</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            description: </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">description</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            state: </span><span style="color: #8FBCBB">GitLabIssueState</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">from</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">state</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            type: </span><span style="color: #8FBCBB">GitLabIssueType</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">from</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">issue_type</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>It is still looking great. We can keep track of all the issue properties a GitLab issue has.</p>
<p>Thanks to the <code>createFromApiPayload</code> method, a GitLab API object is automatically mapped to our structure (no more iid's), keeping it simple for other developers in our team to work with the data without any knowledge of the GitLab internal structure.</p>
<h3>IntegrationIssueDriver</h3>
<p>The <code>IntegrationIssueDriver</code> is the heart of our integration system. It looks (redacted) like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitLabInstalledIntegrationDriver</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">IssueIntegrationDriver</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getIssue</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ProjectIntegration</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">projectIntegration</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitLabIntegrationIssueData</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">ensureProjectIntegrationConfigData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">projectIntegration</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">resolveConnector</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">projectIntegration</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">integrationConnection</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">send</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GetGitLabIssueRequest</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">config</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">project_id</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitLabIntegrationIssueData</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">createFromApiPayload</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">json</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getIssueByUrl</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ProjectIntegration</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">projectIntegration</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">url</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationIssueData</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">preg_match_all</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;/</span><span style="color: #EBCB8B">\/issues\/(?&lt;id&gt;\d</span><span style="color: #81A1C1">+</span><span style="color: #EBCB8B">)</span><span style="color: #81A1C1">$</span><span style="color: #ECEFF4">/m&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">url</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">matches</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">PREG_SET_ORDER</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">matches</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CouldNotMatchUrlToIntegrationIssue</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">url</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getIssue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">projectIntegration</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">NotFoundException</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CouldNotMatchUrlToIntegrationIssue</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">url</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>There are many more methods in the connector than this, but to keep things more readable, we will show these two methods.</p>
<p>The <code>getIssue</code> method returns an <code>IntegrationIssueData</code> object for an issue tied to a Flare project. It makes an API call to GitLab and uses <a href="https://github.com/spatie/laravel-data">laravel-data</a> to map the response to a data object.</p>
<p>See that <code>resolveConnector</code> method? We use the excellent <a href="https://docs.saloon.dev/">Saloon</a> package to have an API abstraction layer between Flare and GitLab. It handles everything from authorization with OAuth, sending requests, receiving responses, and allowing us to use fakes for testing the API. Saloon is excellent. If you haven't, you should start using it now!</p>
<p>The <code>getIssueByUrl</code> method we saw in the previous blog post tries to find an issue by URL and returns an <code>IntegrationIssueData</code> object. After using regex magic, we use the <code>getIssue</code> method to fetch the issue.</p>
<p>We can even abstract more here. In the GitHub, Jira, and thus GitLab connectors, this method always looks the same: use a regex to extract an id and then call the <code>getIssue</code> method with that id returning a data object.</p>
<p>It would require us to transform <code>IssueIntegrationDriver</code> from an interface into an abstract class and add the requirement to implement <code>resolveIssueUrlRegex</code>. Coding this will take little time.</p>
<p>So why didn't we do it? Could our subsequent integration need more complex code to resolve the ID? We don't know it at the moment, but trying to abstract everything away will, in the end, make things a lot harder to implement. A bit of code duplication is not bad in these cases.</p>
<p>When we've implemented more than five integrations, and the code is the same, then that's the moment we'll refactor.</p>
<h2>Webhooks</h2>
<p>In the previous post, we had a long section about Webhooks. External platforms like Jira and GitHub send them to us, allowing Flare to react to changes on these platforms.</p>
<p>At the moment, we do not ingest webhooks from GitLab. That's because, unlike those other platforms, there's no option to subscribe to them automatically.</p>
<p>Webhooks do exist in GitLab, but a user must manually register them. For our GitLab self-hosted customers, this could be an extra step in the wizard when an app is created in their GitLab instance.</p>
<p>For our GitLab cloud customers, the installation process has become much more complex than clicking a button; they now should register webhooks themselves.</p>
<p>We could add the webhooks ourselves since we have access to the API, but adding things to a GitLab instance that isn't ours feels intrusive.</p>
<p>Even worse, somebody could remove those webhooks (intentionally or unintentionally), and the two-way sync would stop working. To do it &quot;right&quot; we need to check around certain intervals if the webhooks still exist and if they are configured correctly.</p>
<p>This complicated webhook setup is why we're not implementing a two-way GitLab sync for now. We do love to listen to our users. Let us know if you want to see a two-way sync in GitLab, which might make the integration a bit more intrusive.</p>
<p>Luckily, we've built the <code>IssueIntegrationDriver</code> so that all the code related to webhooks is defined in another interface called <code>WebhookIntegrationDriver</code>, making it possible to add a new integration to Flare without webhooks support.</p>
<h2>Conclusion</h2>
<p>Our GitLab integration started as an experiment to see if our code was flexible enough to support upcoming integrations. I can tell you it is! We implemented the whole integration in two days!</p>
<p>We love working on Flare and have a lot of new features in the pipeline, heck, maybe even a GitLab two-way sync 😉</p>
]]>
            </summary>
                                    <updated>2023-12-06T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing Jira Integration with Flare]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-jira-integration-with-flare" />
            <id>https://flareapp.io/introducing-jira-integration-with-flare</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Exciting news for all our Flare users! Previously, you could sync <a href="https://flareapp.io/docs/flare/general/github-integration">GitHub</a> issues with Flare errors. Many loved this integration; it allows teams to quickly respond to bugs reported in Flare and keep track of the issue progress in GitHub. When the issue is closed in GitHub, Flare automatically closes the associated error.</p>
<p>We've had countless users asking for a similar feature but with Jira, the all-in-one bug tracking and project management solution from Atlassian. Just like GitHub, it allows you to create issues for bugs and track the progress your team has made in resolving them.</p>
<p>We're proud to present the Jira integration within Flare today! Let's go through some of the features.</p>
<h2>Integrating Jira projects with Flare projects</h2>
<p>You get started by connecting a Jira team with your Flare team. After that, within the Flare project settings, you can connect a Jira project with a Flare project:</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-jira/jira-add-project.webp" alt="" /></p>
<p>You also need to select an issue type that will be used to report errors.</p>
<p>When you've connected the Jira and Flare projects, you will be able to configure how the integration will work:</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-jira/jira-project-settings.webp" alt="" /></p>
<ul>
<li>
<p>Should Flare URLs in Jira issues automatically link the Flare error with the Jira issue?</p>
</li>
<li>
<p>When an issue status is changed to or from a certain status, should the Flare error be resolved or unresolved?</p>
</li>
<li>
<p>Should the Jira issue change to a certain status when a Flare error is resolved or unresolved?</p>
</li>
</ul>
<p>All these options are configurable! It is even possible to connect multiple Jira projects with one Flare project.</p>
<h2>Jira in Flare</h2>
<p>When you've configured your Flare project to work with Jira, you'll get a new button on each error:</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-jira/jira-linked-issues.webp" alt="" /></p>
<p>Here, you can see all the Jira issues connected with this error and immediately create a new issue:</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-jira/jira-create-issue.png" alt="" /></p>
<p>Already have an issue within Jira? No problem! Just use the URL of the issue to link it with the error:</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-jira/jira-link-url.webp" alt="" /></p>
<p>When an issue is linked to an error, we automatically track it. When the status changes, you'll see this reflected in Flare. That's neat!</p>
<h2>Flare in Jira</h2>
<p>Lastly, when in Jira, you'll see a new panel on your issue detail view called &quot;Flare.&quot; Here, you can see all the Flare errors this issue is linked with:</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-jira/jira-linked-flare-errors.webp" alt="" /></p>
<h2>Conclusion</h2>
<p>We're thrilled to release this Jira integration finally! You'll find more information about it <a href="https://flareapp.io/docs/flare/general/jira-integration">here</a>. Check it out and let us know what you think about it.</p>
<p>As a small teaser for next week, we will reveal another integration. Stay tuned!</p>
]]>
            </summary>
                                    <updated>2023-11-30T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[You can now give team members access to specific projects]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/you-can-now-give-team-members-access-to-specific-projects" />
            <id>https://flareapp.io/you-can-now-give-team-members-access-to-specific-projects</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>On most projects, you're probably not working alone. That's why Flare has been team-based from day one. Everybody invited to your team on Flare can see all the projects you have in your account.</p>
<p>For small teams, this mostly works fine. When you have a bigger team, people often don't need access to all projects in your Flare account but only a few specific ones they are working on.</p>
<p>That's why we've now added the ability to give team members access to specific projects. In the team settings, you can specify which projects each of your team members should be able to see.</p>
<p><img src="https://content.spatie.be/assets/permissions.jpg" alt="" /></p>
<p>Remember that Flare, as an error tracker, contains potentially sensitive information. So it's also good practice to only expose that information to people who really need to see it.</p>
]]>
            </summary>
                                    <updated>2023-11-09T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Leveraging Cloudflare Workers for Edge API Authentication]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/leveraging-cloudflare-workers-for-edge-api-authentication" />
            <id>https://flareapp.io/leveraging-cloudflare-workers-for-edge-api-authentication</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Currently, there is at least a <a href="https://www.cloudping.co/grid">±70ms latency between the EU and US data centers</a>, which is unlikely to improve significantly due to the limitations imposed by the speed of light and other annoying physics-based factors. The internet could be a lot faster if we took all the people and servers and put them on the same continent but that brings other issues (where would I get my free healthcare?). Fortunately, edge computing allows you to bring your web app closer to your customers without having to organise a mass relocation.</p>
<p>Cloudflare and AWS can leverage their existing network and datacenters all over the world to bring your application to edge locations, as close to your users as possible. In this article, we'll explore how Cloudflare Workers can be used to deploy a small serverless function that performs API authentication on the edge, providing an effective solution to drop unauthorised and invalid requests in a high-traffic environment like Flare.</p>
<h2>Why perform API auth on edge at all?</h2>
<p>Flare receives a lot of invalid requests. About 40% of all incoming API requests contain invalid, expired or no API keys. In an ideal world, 100% of our CPU time (and budget) should go to handling valid requests from paying customers and not to rejecting unauthorised requests. By placing a cheap and fast edge function in front of our API, we can reject 40% of incoming traffic before it ever hits our ingress servers.</p>
<p>Keep in mind that we’re authenticating the incoming API requests on our own servers as well. Performing authentication twice may appear counter-intuitive, but the actual API endpoints remain publicly accessible if you manage to bypass the edge function and access the API directly. Additionally, keeping the authentication in the application code keeps the application nice and simple to run and debug locally.</p>
<h2>Choosing Cloudflare Workers</h2>
<p>After careful consideration (read: a panicked evening during a suspected DDoS attack) we decided on using Cloudflare’s excellent (and free) DDoS protection and WAF. When the time came to investigate serverless functions for the aforementioned API auth, we quickly decided on Cloudflare Workers too. They proved to be a good choice with instant cold starts, reasonable pricing, global availability (thanks to their edge-based nature), and easy integration with existing infrastructure on Cloudflare.</p>
<h2>Let’s write a basic authentication Worker</h2>
<p>Let’s put the theory into practice. Like the name suggests, an edge function is not much more than a function. The code for a very basic Cloudflare Worker looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">default</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fetch</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Request</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Promise</span><span style="color: #ECEFF4">&lt;</span><span style="color: #D8DEE9FF">Response</span><span style="color: #ECEFF4">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Response</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Hello world!</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>To authenticate the API request here, we should check the API key in the <code>request.headers</code>. If everything checks out we can use the <code>fetch</code> API to send the request to our actual API endpoint:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">default</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fetch</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Request</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Promise</span><span style="color: #ECEFF4">&lt;</span><span style="color: #D8DEE9FF">Response</span><span style="color: #ECEFF4">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">apiKey</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">headers</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">get</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">X-Api-Key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">authenticate</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">apiKey</span><span style="color: #D8DEE9FF">)) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Response</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Invalid API key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">status</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">403</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// forward the `request` to example.com/api</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fetch</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">https://example.com/api</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">request</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<h2>Cloudflare Workers KV</h2>
<p>To authenticate the <code>apiKey</code>, it might be tempting to connect to your MySQL database and check the appropriate tables. However, because we’re executing this code on the edge, the database might be many milliseconds away, resulting in slow queries! Additionally, opening a many new database connection for all incoming API requests would undermine the purpose of having API authentication on the edge anyways.</p>
<p>Instead of hitting the database directly, we can leverage Cloudflare Workers KV, a low-latency and globally synced key-value store. It’s also accessible from the Cloudflare API, allowing us to sync a list of all active API keys to Workers KV, directly from a CRON job in our application. Accessing the Workers KV store from our Cloudflare Worker is made really easy with <a href="https://developers.cloudflare.com/kv/learning/kv-bindings/">KV bindings</a>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">default</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fetch</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Request</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #D8DEE9">env</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Env</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Promise</span><span style="color: #ECEFF4">&lt;</span><span style="color: #D8DEE9FF">Response</span><span style="color: #ECEFF4">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// env.ApiKeys is bound to the KV store:</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">apiKeys</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">await</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">env</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">ApiKeys</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">&lt;</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF">[]</span><span style="color: #ECEFF4">&gt;</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">api_keys</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #88C0D0">type</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">json</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">||</span><span style="color: #D8DEE9FF"> []</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">apiKey</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">headers</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">get</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">X-Api-Key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">apiKeys</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">includes</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">apiKey</span><span style="color: #D8DEE9FF">)) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">Response</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Invalid API key</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #88C0D0">status</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">403</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fetch</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">https://example.com/api</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">request</span><span style="color: #D8DEE9FF">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Additionally, Cloudflare Worker cache can be used to decrease KV costs and latency even more.</p>
<h2>Getting ready for production</h2>
<p>Of course, there’s more to it than just these 20 lines of code. For Flare, we have additional code in place to <a href="https://developers.cloudflare.com/workers/examples/cors-header-proxy/">handle CORS requests</a>, <a href="https://developers.cloudflare.com/workers/examples/cache-using-fetch/">cache API keys using Worker cache</a>, and perform extra validation.</p>
<p>To deploy our Cloudflare Worker, we can use Cloudflare’s <code>wrangler</code> CLI. Using a simple config file and the <code>wrangler</code> command, it’s really easy to deploy to multiple environments with different environment variables, run the function locally, or tail the production logs. The config file also defines the <code>routes</code> that the Worker function will be active on. This allows us to essentially use the Worker as a middleware for an existing route.</p>
<h2>I swear this is not a sales pitch</h2>
<p>Cloudflare deserves more credit for everything they offer in their free tier. With DDoS protection, a basic WAF, and an abundance of free Worker function invocations (they're practically throwing them at us), load on our ingress servers has drastically reduced. Additionally, we now have the ability to more easily manage and route traffic through code, should we feel the need.</p>
]]>
            </summary>
                                    <updated>2023-11-05T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Our Slack integration now supports unfurling]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/our-slack-integration-now-supports-unfurling" />
            <id>https://flareapp.io/our-slack-integration-now-supports-unfurling</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<h2>What is Slack unfurling?</h2>
<p>&quot;Unfurling&quot; is what Slack calls adding a small preview to a link that is posted in your workspace. For example, a preview with the content of a Tweet, or the social image &amp; description of a web page.</p>
<p>This all works well until you try to share links that are private, and thus result in a 404, 403 or 401 error code for the Slack bot that is processing your link.</p>
<p>Luckily, Slack provides a way for your app to hook into links that are posted with your domain and lets you generate that preview yourself.</p>
<h2>What it looks like in our integration</h2>
<p>With our Flare Slack app, you can now unfurl urls to:</p>
<h3>Projects</h3>
<p><img src="https://content.spatie.be/assets/flare/our-slack-integration-now-supports-unfurling/zgovch29jngcl9j28ved5lioikeegfrgeb26rxxf.png" alt="" /></p>
<h3>Errors &amp; Error Occurrences</h3>
<p><img src="https://content.spatie.be/assets/flare/our-slack-integration-now-supports-unfurling/mtkxmlew3ibggg1bsig0sa0zwvu2wypuori7setu.png" alt="" /></p>
<h2>Getting started</h2>
<p>The first thing you'll need is a <a href="https://api.slack.com/apps">Slack app</a> with the correct <code>link:read</code> &amp; <code>link:write</code> permissions.</p>
<p>Once you have that set up, you can head to &quot;Event subscriptions&quot; and enable events. This will require a request URL that needs to be verified.</p>
<p>The code to handle this verification is pretty straightforward, Slack sends you a request with a type of <code>url_verification</code> and you reply back with the challenge value after verifying that the signature is correct.</p>
<p>Here's a bit of example code:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SlackWebhookController</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__invoke</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SlackRequest</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">type</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">url_verification</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">response</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">json</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">challenge</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">challenge</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// Other event types</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Inside our <code>SlackRequest</code> class we'll verify the signature, you can find the signing secret in your Slack app's credentials page:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SlackRequest</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">FormRequest</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">authorize</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">timestamp</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">header</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">X-Slack-Request-Timestamp</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackSignature</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">header</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">X-Slack-Signature</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">body</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getContent</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">requestSignature</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">v0:</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">timestamp</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">:</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">body</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">requestSignature</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">v0=</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">hash_hmac</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">sha256</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">requestSignature</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> config</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">services.slack.signing_secret</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">hash_equals</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">requestSignature</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackSignature</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<h2>Authenticating users</h2>
<p>Before your Slack app is able to handle unfurling, you'll need your users to be able to install the app into their workspace. Slack provides an oauth flow for this. Luckily Laravel has a first party package called <a href="https://laravel.com/docs/10.x/socialite">Socialite</a> that is able to handle most of the difficult work for this.</p>
<p>Check out the <a href="https://laravel.com/docs/10.x/socialite">Socialite docs</a> on how to get set up.</p>
<p>We'll create a <code>SlackAuthorizationController</code> that will handle the installation of our Slack app into the workspace of our users.</p>
<p>The first step is to create a redirect to Slack's oauth url:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SlackAuthorizationController</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">authorize</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RedirectResponse</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Socialite</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">driver</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">slack</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">asBotUser</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">setScopes</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">links:read</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">links:write</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">redirectUrl</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">action</span><span style="color: #ECEFF4">([</span><span style="color: #81A1C1">self::class</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">callback</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]))</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">redirect</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>As you can see, Socialite makes this very straightforward, the important part here is the <code>-&gt;asBotUser()</code> call which will need to be repeated when fetching the token in the callback part.</p>
<p>The <code>-&gt;redirectUrl()</code> method calls for a <code>callback()</code> method, let's create that one to fetch the bot token and save the necessary information to facilitate our unfurling.</p>
<p>I've added comments in the code to clarify what is happening:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SlackAuthorizationController</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">callback</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RedirectResponse</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #D8DEE9">         Here we fetch the user from the code that is returned by Slack in the url.</span></span>
<span class="line"><span style="color: #D8DEE9">         As before, Socialite makes this easy for us. We do have to make sure</span></span>
<span class="line"><span style="color: #D8DEE9">         that we set up the driver with the same scopes and redirectUrl.</span></span>
<span class="line"><span style="color: #616E88">        **/</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Socialite</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">driver</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">slack</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">asBotUser</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">setScopes</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">links:read</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">links:write</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">redirectUrl</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">action</span><span style="color: #ECEFF4">([</span><span style="color: #81A1C1">self::class</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">callback</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]))</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">user</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">token</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #D8DEE9">         You can handle the error case here, this usually happens</span></span>
<span class="line"><span style="color: #D8DEE9">         when the user cancels the authentication flow.</span></span>
<span class="line"><span style="color: #616E88">        **/</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #D8DEE9">         Next, we&#39;ll use the auth.test endpoint from Slack to make sure</span></span>
<span class="line"><span style="color: #D8DEE9">         the token works, from this endpoint we also receive more</span></span>
<span class="line"><span style="color: #D8DEE9">         information about which team &amp; user installed the app.</span></span>
<span class="line"><span style="color: #616E88">        **/</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Http</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">withToken</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">token</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">post</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">https://slack.com/api/auth.test</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">json</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #D8DEE9">         We&#39;ll create a link between the logged in user, its Slack user_id</span></span>
<span class="line"><span style="color: #D8DEE9">         and the team_id from the team the app was installed in.</span></span>
<span class="line"><span style="color: #616E88">        **/</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">SlackAuthentication</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">firstOrCreate</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">user_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Auth</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">user</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">slack_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">user_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #D8DEE9">         We also want to keep track of active Slack app installations</span></span>
<span class="line"><span style="color: #D8DEE9">         and the token that is assigned to them. Keep in mind that</span></span>
<span class="line"><span style="color: #D8DEE9">         the token needs to be stored encrypted in the database.</span></span>
<span class="line"><span style="color: #616E88">        **/</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">SlackInstallation</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">updateOrCreate</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">authed_user_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">user_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">response</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">],</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">access_token</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">token</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">redirect</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">action</span><span style="color: #ECEFF4">([</span><span style="color: #8FBCBB">SlackSettingsController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">index</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>We keep separate <code>SlackAuthentication</code> and <code>SlackInstallation</code> records, because once a Slack app is installed in the workspace, other users in the workspace need to be able to link their Slack user ID to the team_id as well.</p>
<p>Once the Slack app is installed into a team's workspace, we can start handling the <code>link_shared</code> events.</p>
<h2>Handling the <code>link_shared</code> event</h2>
<p>After all this set up, we're finally ready to start handling the <a href="https://api.slack.com/events/link_shared">link_shared</a> event.</p>
<p>When this event comes in, you'll need to check if there is an installed app for the team, and we can find the user in our Slack authentications.</p>
<p>A simplified example from our webhook controller:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SlackWebhookController</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__invoke</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SlackRequest</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">event</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">type</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">link_shared</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackInstallation</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SlackInstallation</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">first</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// If we can&#39;t find a Slack installation, just respond with a 200</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackInstallation</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">response</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">all</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackUserId</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">authorizations</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">user_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">whereHas</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">slackAuthentications</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackUserId</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">slack_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackUserId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">first</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #D8DEE9">                If we don&#39;t find any authenticated users with this Slack id</span></span>
<span class="line"><span style="color: #D8DEE9">                send an authentication message instead. We&#39;ll cover this</span></span>
<span class="line"><span style="color: #D8DEE9">                below.</span></span>
<span class="line"><span style="color: #616E88">            **/</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">handleUserAuthentication</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackId</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackInstallation</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #D8DEE9">                Create an array of the urls in the message</span></span>
<span class="line"><span style="color: #616E88">            **/</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">urls</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">array_map</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">link</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">link</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">url</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">event</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">links</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #D8DEE9">                Dispatch a job that handles the unfurling of the URL</span></span>
<span class="line"><span style="color: #616E88">            **/</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">dispatch</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">()</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">use</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackUserId</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #88C0D0">                app</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">UnfurlUrlsAction</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #88C0D0">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">teamId</span><span style="color: #ECEFF4">&#39;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #88C0D0">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">userId</span><span style="color: #ECEFF4">&#39;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackUserId</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">urls</span><span style="color: #ECEFF4">&#39;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">urls</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">unfurlId</span><span style="color: #ECEFF4">&#39;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">event</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">unfurl_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #88C0D0">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">source</span><span style="color: #ECEFF4">&#39;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">event</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">source</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #88C0D0">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">messageTs</span><span style="color: #ECEFF4">&#39;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">event</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">message_ts</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #88C0D0">                </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">            </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">response</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<h3>Handling user authentication</h3>
<p>Slack provides a way for your app to let the user know that unfurling is available inside their workspace, but they haven't authenticated yet. This is what we're calling in the <code>$this-&gt;handleUserAuthentication()</code> method above:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handleUserAuthentication</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackId</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SlackInstallation</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackInstallation</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Response</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">Http</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">withToken</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackInstallation</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">access_token</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">post</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">https://slack.com/api/chat.unfurl</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">unfurl_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">event</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">unfurl_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">source</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">event</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">source</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">ts</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">event</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">message_ts</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">user_auth_url</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">action</span><span style="color: #ECEFF4">([</span><span style="color: #8FBCBB">SlackAuthorizationController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">linkUserToTeam</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #88C0D0">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">slackId</span><span style="color: #ECEFF4">&#39;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackId</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">teamId</span><span style="color: #ECEFF4">&#39;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackInstallation</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">team_id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">                </span><span style="color: #ECEFF4">]),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">throw</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ok</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>We use the SlackInstallation's token to post to the <code>chat.unfurl</code> endpoint, with a <code>user_auth_url</code> parameter. This displays the following message to the user:</p>
<p><img src="https://content.spatie.be/assets/flare/our-slack-integration-now-supports-unfurling/m71rzs2yfb6gvmufh49dnhbeixux5u2ij8w9ydu1.png" alt="" /></p>
<p>Once they click the link, we link the user to the team by creating a new SlackAuthentication:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">linkUserToTeam</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackId</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamId</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">Auth</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">user</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">slackAuthentications</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">firstOrCreate</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">slack_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackId</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamId</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">redirect</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">action</span><span style="color: #ECEFF4">([</span><span style="color: #8FBCBB">SlackSettingsController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">index</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>In our full integration we go a small step further and then unfurl the URL they just tried to send, but this is beyond the scope of this blogpost.</p>
<h3>Unfurling the URL</h3>
<p>The final step is actually unfurling the URL that the user has posted. This can be done by calling the <code>chat.unfurl</code> API method with the following data:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #8FBCBB">Http</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">withToken</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">slackInstallation</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">access_token</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">contentType</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">application/json; charset=utf-8</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">post</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">https://slack.com/api/chat.unfurl</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">unfurl_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">unfurlData</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">unfurlId</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">source</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">unfurlData</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">source</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">ts</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">unfurlData</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">messageTs</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">unfurls</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">https://example.com</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">blocks</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">...</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">throw</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>The important part here is the <code>unfurls</code> parameter, this is an array of data keyed by the URLs that you were able to unfurl. For more information about the <code>blocks</code> I suggest <a href="https://api.slack.com/block-kit">Slack's great documentation</a> on them.</p>
<p>The parsing from a URL to which preview you want to generate is very app-specific, so I won't go into detail about this.</p>
]]>
            </summary>
                                    <updated>2023-10-16T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Building a flexible yet abstract external integrations Structure]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/building-a-flexible-yet-abstract-external-integrations-structure" />
            <id>https://flareapp.io/building-a-flexible-yet-abstract-external-integrations-structure</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Here we are. Flare is better than ever! We've been adding features week after week. Yet, one significant request on our <a href="https://github.com/spatie/flareapp.io-roadmap/discussions">roadmap</a> remains: Jira! It is the most upvoted request on our roadmap, which is something we can't ignore.</p>
<p>We already had a GitHub integration. It allows you to <a href="/docs/flare/general/github-integration">link GitHub</a> issues and PRs to Flare errors. What's cool is that you can automatically close an issue when an error is resolved. The architecture behind this was built over two years ago, and we thought we had built it so that we could plug in another issue-tracking system like Jira without any problems. Oh boy, were we wrong about that.</p>
<p><img src="https://content.spatie.be/assets/flare/building-a-flexible-yet-abstract-external-integrations-structure/github-menu.jpg" alt="github example" /></p>
<p>So, building the Jira integration opened up an opportunity to do it better, and that's just what we did. We built a flexible structure that abstracts much of the code shared among integrations, allowing us to add new integrations quickly. We'll go through three examples where we've abstracted a lot of code while still making it possible to implement completely different integrations.</p>
<h2>Requirements</h2>
<p>At its simplest, an issue tracker is an external service with a few groups (projects might be a better term here, but this clashed too much within our code base with Flare projects). For GitHub, a group is a repository; for Jira, this is a project. Each group has issues within them. These issues have a title and a body where the issue is summarized. Most of the time, these issues also have comments with a body.</p>
<h2>Issue Data</h2>
<p>We want to link a Flare error with an issue in an integration. So, somewhere within our database table, we need to store this link. A possible schema will probably look like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">id</span></span>
<span class="line"><span style="color: #D8DEE9FF">error_id</span></span>
<span class="line"><span style="color: #D8DEE9FF">integration_type</span></span>
<span class="line"><span style="color: #D8DEE9FF">integration_id</span></span>
<span class="line"></span></code></pre>
<p>Because we want to show some information about linked issues on an error, we need a data structure for them:</p>
<p>For Jira, the structure would look like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">JiraIntegrationIssueData</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">summary</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">description</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">status</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reporter</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">assignee</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">creator</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">    </span></span>
<span class="line"></span></code></pre>
<p>For GitHub, it would look like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitHubIntegrationIssueData</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">number</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">title</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">body</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitHubIssueType</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">type</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitHubIssueStatus</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">status</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #81A1C1">string[]</span><span style="color: #616E88"> */</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">assignees</span><span style="color: #ECEFF4">,</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">    </span></span>
<span class="line"></span></code></pre>
<p>Apart from the id, these structures are entirely different. We could create separate tables for them and then use a polymorphic relation to these structures. But that means we need to create two more extra tables + two more extra models just for this metadata. At the same time, these data objects contain all the structured data.</p>
<p>Even worse, because we need to link a Flare project to an integration group, we'll need some extra metadata here, so add two tables and two models. To finish it off, a team is linked with an integration instance. For example, you link your Flare team with a GitHub organization or account. Again, we need to store data about this; we need to store some configuration (like the name of the GitHub organization user) and secrets (API keys). This means we'll need to add another four tables and four models to make the Jira and GitHub integrations work.</p>
<p>Adding an extra integration would require four tables and models when we use this approach. We plan to add more integrations, so we would need twenty tables and models to store data when we have five integrations. That's an awful lot of extra models and tables!</p>
<p>Luckily, there's another way. Let's update our schema and add a JSON field called data:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">id</span></span>
<span class="line"><span style="color: #D8DEE9FF">error_id</span></span>
<span class="line"><span style="color: #D8DEE9FF">integration_type</span></span>
<span class="line"><span style="color: #D8DEE9FF">integration_id</span></span>
<span class="line"><span style="color: #D8DEE9FF">data</span></span>
<span class="line"></span></code></pre>
<p>We could try to cast the <code>JiraIntegrationIssueData</code> and <code>GitHubIntegrationIssueData</code> objects ourselves because we need the functionality to cast them to a JSON payload and back into an object again. Luckily, we don't need to write code for this because we can use the excellent <a href="https://github.com/spatie/laravel-data">laravel-data</a> package to do this for us.</p>
<p>Letting our classes <code>JiraIntegrationIssueData</code> and <code>GitHubIntegrationIssueData</code> extend from data adds a lot of functionality. What's most useful for us is that we can call <code>$someDataObject-&gt;toJson()</code> on our data objects, and they will be automatically cast into a JSON string. Using <code>SomeIntegrationIssueData::from($json)</code> then allows us to create a data object again from JSON, neat!</p>
<p>But we're not done here. We still need a cast for our <code>ErrorIntegrationIssue</code> model representing the link between an error and an integration issue. The data field of the model can be <code>JiraIntegrationIssueData</code>, <code>GitHubIntegrationIssueData</code>, or even some completely new object type that we'll build for a new integration in the future.</p>
<p>Luckily, laravel-data saves us again here. We create an abstract <code>IntegrationIssueData</code> class like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">abstract</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationIssueData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Data</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getId</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The data classes then would look like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">JiraIntegrationIssueData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">IntegrationIssueData</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// Properties here</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getId</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// and</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitHubIntegrationIssueData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">IntegrationIssueData</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// Properties here</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getId</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>When we create a new link as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">gitHubIntegrationIssueData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitHubIntegrationIssueData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">...</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorIntegrationIssue</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ErrorIntegrationIssue</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">error_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">integration_type</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationType</span><span style="color: #81A1C1">::</span><span style="color: #D8DEE9FF">GitHub</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">integration_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">gitHubIntegrationIssueData</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">data</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">gitHubIntegrationIssueData</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>And retrieve somewhere later in our code, we can do the following:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorIntegrationIssue</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">data</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// a GitHubIntegrationIssueData instance</span></span>
<span class="line"></span></code></pre>
<p>How does this magic work? We've added the following cast to <code>ErrorIntegrationIssue</code>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ErrorIntegrationIssue</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Model</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">casts</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">data</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationIssueData</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// other model stuff</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Since <code>IntegrationIssueData</code> is abstract, laravel-data knows that each object being saved to the database extends from that abstract data object. So, we also store the class name next to the data from the data object. When retrieving the model and thus data object again from the database, laravel-data will create a new object based on that stored class name and with the stored data.</p>
<h3>Considerations</h3>
<p>The database people are probably already screaming; these data structures all deserve their own table with their own fields. And most of the time, I would agree with them, but not in this case. We don't need to query these data objects; they are only shown on an error page. We can fetch them by looking for issues linked to the error by checking the <code>error_id</code> field.</p>
<p>In one case, we need to query a field from the data object where a webhook comes in, for example, because the issue's title changed. In this case, we need to know that the issue is from a specific integration (this would most certainly mean that we need to check the class name of the data object to determine if it's Jira or GitHub) and its id so that we can update the issue data object.</p>
<p>That's why we have the <code>integration_type</code> and <code>integration_id</code> fields on <code>ErrorIntegrationIssue</code>. When a webhook comes in, we can use these (indexed) fields to quickly look up the issue and then update the data object.</p>
<h2>Communicating with APIs</h2>
<p>Let's look at another part of our application where we tried to create a flexible abstract structure. A new feature we're adding with these integrations is linking a Flare error with an issue using a URL. On an error, you'll have a text field in which you can put a URL to a GitHub or Jira issue, and by clicking a &quot;link&quot; button, these issues will be magically linked.</p>
<p>The process behind this has three steps:</p>
<ol>
<li>
<p>Find the issue based on a URL</p>
</li>
<li>
<p>Add a new entry to <code>ErrorIntegrationIssue</code> with the data from the integration issue</p>
</li>
<li>
<p>Add a comment to the issue that we've linked to it</p>
</li>
</ol>
<p>We chose a driver-based design for this. We created an interface called <code>IssueIntegrationDriver</code>, which looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">interface</span><span style="color: #D8DEE9FF"> IssueIntegrationDriver</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getType</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationType</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getIssueByUrl</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">url</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationIssueData</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">createIssueComment</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">issueId</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">body</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Each integration will have its driver, so we have a <code>GitHubIntegrationDriver</code> and a <code>JiraIntegrationDriver</code>. The <code>getIssueByUrl</code> method will first parse the string URL given and try to find the issue id (for a GitHub URL like https://github.com/spatie/flareapp.io/issues/481, this would be 481). Then, we'll use the provided Flare project and the Flare team linked to it to fetch the required data for the API (the repository, GitHub owner of the repository, and API tokens) and call the GitHub API to fetch the issue.</p>
<p>Next, a <code>GitHubIntegrationIssueData</code> object is created from the API issue, which saves an <code>ErrorIntegrationIssue</code> model. Lastly, we call <code>createIssueComment</code> and use the URL to the Flare error as a body.</p>
<p>All of this looks like this action:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">LinkIntegrationIssueWithErrorAction</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Error</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">url</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ErrorIntegrationIssue</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getIssueDriver</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getIssueByUrl</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">url</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorIntegrationIssue</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ErrorIntegrationIssue</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">error_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">integration_type</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getType</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">integration_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getId</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">data</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">flareUrl</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ErrorLinksData</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">from</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">show</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">createIssueComment</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorIntegrationIssue</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">integration_id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Issue was linked with Flare error: [</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">exception_message</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">](</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">flareUrl</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">)</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorIntegrationIssue</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This action is tied to a controller, which first checks if there's an issue in the integration for the URL; otherwise, a validation error is shown. The beauty of this approach is that we're done here. This code should not be changed when we want to add another integration. We simply implement the interface, and that's it. The feature works!</p>
<h3>Considerations</h3>
<p>Again, there are a few considerations to be made. First, the drivers will become large when we count line numbers. Our GitHub driver is 360 lines, and our Jira driver is 380. They will grow bigger in the future when we add even more features.</p>
<p>Secondly, we briefly discussed the abstract data structures beside <code>IntegrationIssueData</code>. These are <code>ProjectIntegrationConfigData</code> on a project and two structures on a Flare team called: <code>IntegrationConnectionSecretsData</code> and <code>IntegrationConnectionConfigData</code>. A <code>Project</code> comes in with <code>ProjectIntegrationConfigData</code> in the interface methods. We don't know if it's <code>GitHubProjectIntegrationConfigData</code> or <code>JiraProjectIntegrationConfigData</code>.</p>
<p>This means that for a lot of methods, we'll have the following code:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">createIssueComment</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">Project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">issueId</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">body</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">instanceof</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">JiraProjectIntegrationConfigData</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Exception</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Invalid config</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// the API calling stuff</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This code is required to ensure that the correct instances are passed to the methods and adds IDE completion, which is the most significant benefit. It gets even worse when we also need to check <code>IntegrationConnectionSecretsData</code> and <code>IntegrationConnectionConfigData</code>, sometimes adding a whopping three extra checks making our method less readable.</p>
<p>To make the code a bit more readable, we've added some helper methods like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">ensureProjectConfigData</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">JiraProjectIntegrationConfigData</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">instanceof</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">JiraProjectIntegrationConfigData</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Exception</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Invalid config</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">config</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Making the method a bit more readable:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">createIssueComment</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">Project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">issueId</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">body</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">ensureProjectConfigData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// the API calling stuff</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>I don't think these two disadvantages weigh up against all the &quot;free&quot; functionality we get by using this abstraction. Adding a new integration is as easy as implementing the interface for the integration, and we're done.</p>
<h2>Processing Webhooks</h2>
<p>Lastly, webhooks. These differ significantly between integrations; they have other methods for verifying the webhook is sent from the integration itself and not some bad actor. The payload of an issue created webhook looks different between each integration. Also, when a webhook is sent, it can differ depending on the integration.</p>
<p>But is this really true? In the end, we can see that there is a common ground for all the integrations; each of these integrations sends an event in these cases:</p>
<ul>
<li>
<p>group created/updated/deleted</p>
</li>
<li>
<p>issue created/updated/deleted</p>
</li>
<li>
<p>issue comment created/updated/deleted</p>
</li>
</ul>
<p>Can we also make abstractions here?</p>
<p>We create a specific webhook controller for each integration. Since these integrations all have specific verification systems, we can't go around this. What we can do is create a generalized <code>IntegrationWebhook</code> model. It keeps track of a few things:</p>
<ul>
<li>
<p>Which connection to an integration on a Flare team does the webhook belong to</p>
</li>
<li>
<p>Possibly the project the webhook is connected to (not relevant to groups)</p>
</li>
<li>
<p>A unique webhook id so that a webhook is only processed once</p>
</li>
<li>
<p>The payload of the webhook request</p>
</li>
<li>
<p>The headers of the webhook request</p>
</li>
</ul>
<p>A webhook controller for GitHub now looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitHubWebhookController</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__invoke</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Request</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">IngestIntegrationWebhookAction</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">ingestIntegrationWebhookAction</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">isSignatureValid</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">response</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">json</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">message</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Invalid signature</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Response</span><span style="color: #81A1C1">::</span><span style="color: #D8DEE9FF">HTTP_FORBIDDEN</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">all</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">connection</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationConnection</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">type</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationConnectionType</span><span style="color: #81A1C1">::</span><span style="color: #D8DEE9FF">GitHub</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">identifier</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">installation</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">first</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">connection</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">response</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">json</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">message</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Connection not found</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">200</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">message</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">ingestIntegrationWebhookAction</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">connection</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">headers</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">X-GitHub-Delivery</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">repository</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">response</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">json</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">message</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">message</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">isSignatureValid</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Request</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// Some code to check the request</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The <code>IngestIntegrationWebhookAction</code> has four parameters:</p>
<ul>
<li>
<p><strong>connection</strong> the connection on the team with the integration (stores all API secrets and config)</p>
</li>
<li>
<p><strong>request</strong> a Laravel request object</p>
</li>
<li>
<p><strong>webhookId</strong> a unique id provided by GitHub so that we don't process the webhook twice</p>
</li>
<li>
<p><strong>integrationGroupId</strong> is a nullable id to a group we use to find the project. In the case of GitHub, this is the repository name</p>
</li>
</ul>
<p>The <code>IngestIntegrationWebhookAction</code> will now create the <code>IntegrationWebhook</code> model or stop if we already have an <code>IntegrationWebhook</code> model with the webhookId due to an earlier webhook being sent.</p>
<p>It tries to find a related project when <code>integrationGroupId</code> is provided and will trigger a job where the webhook will be handled so that the whole request to this controller is quick so that we can send a 200 response to the integration in a matter of milliseconds.</p>
<p>Within the job, the specific <code>IssueIntegrationDriver</code> for the integration based upon the connection is retrieved, and <code>getWebhookHandler</code> is called. This method will return an <code>IntegrationWebhookHandler</code> again an interface so that we'll have a <code>JiraIntegrationWebhookHandler</code> and <code>GitHubIntegrationWebhookHandler</code> and even more when we implement more integrations. These handlers have two tasks: trigger events that will interact with <code>ErrorIntegrationIssue</code> and ensure our system stays in sync with the integration.</p>
<p>When do we need to keep staying in sync? For example, we save the repository's name on the Flare project with the GitHub integration. But, a repository name can be changed in the GitHub UI. So when that happens, all the API requests using the old repository name will fail. That's why we're reacting to the GitHub repository <code>renamed</code> event and update the project. These actions are not required with Jira, where projects are identified by an id that cannot change.</p>
<p>On the other hand, we can still abstract away some code. When an issue is created or updated, it doesn't matter if it happened within GitHub or Jira. We only need to know when the issue was created or updated and with which data. That's why we can often send out an <code>IntegrationEvent</code>, which will be handled by our <code>IntegrationEventHandler</code>. If an issue is created or updated, we will trigger an <code>IntegrationIssueUpdatedOrCreated</code> event.</p>
<p>A simplified version of the <code>GitHubIntegrationWebhookHandler</code> would look like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GitHubIntegrationWebhookHandler</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">IntegrationWebhookHandler</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">IntegrationConnection</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">integrationConnection</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">Project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">IntegrationWebhook</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">webhook</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">HeaderBag</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">headers</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">event</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">headers</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">X-GitHub-Event</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">action</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">action</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">event</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">repository</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">in_array</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">action</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">opened</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">edited</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">closed</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">reopened</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">assigned</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">unassigned</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handleRepositoryRenamed</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">integrationConnection</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">headers</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">action</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">WebhookNotMatched</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">noProjectFound</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">headers</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">in_array</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">event</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">issues</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">pull_request</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #88C0D0">            </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #88C0D0"> in_array</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">action</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">opened</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">edited</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">closed</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">reopened</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">assigned</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">unassigned</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handleIssueUpdatedOrCreated</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">integrationConnection</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">headers</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">action</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">WebhookNotMatched</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">invalidEvent</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">headers</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handleRepositoryRenamed</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">IntegrationConnection</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">integrationConnection</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationGroupUpdated</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Project</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">integration_connection_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">integrationConnection</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">integration_group_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">changes</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">repository</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">from</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">each</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">GitHubProjectIntegrationConfigData</span><span style="color: #616E88"> $config */</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">config</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">config</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">repository</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">repository</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">config</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">integration_group_id</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">repository</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">save</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationGroupUpdated</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">integrationConnection</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #8FBCBB">GitHubIntegrationGroupData</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">createFromWebhook</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handleIssueUpdatedOrCreated</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">IntegrationConnection</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">integrationConnection</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Project</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationIssueUpdatedOrCreated</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">GitHubProjectIntegrationConfigData</span><span style="color: #616E88"> $config */</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">config</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">config</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">IntegrationIssueUpdatedOrCreated</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">integrationConnection</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #8FBCBB">GitHubIntegrationIssueData</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">createFromWebhook</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Lastly, let's quickly look at the <code>IntegrationEventHandler</code>. It will listen to events and react accordingly. For example, when a <code>handleIntegrationGroupDeleted</code>, we'll delete all <code>ErrorIssueIntegration</code> models for that group since it no longer exists. When an <code>IntegrationIssueUpdatedOrCreated</code> is triggered, we start looking for existing <code>ErrorIssueIntegration</code> models and update them for that issue. If there is a link to a Flare error in the created or updated issue title or body, we'll link them together.</p>
<p>We react to even more events in the <code>IntegrationEventHandler</code>, which means we don't have to write this code for each integration, which is excellent! Fixing issues in this handler will also fix issues with each integration instead of fixing each individually.</p>
<h3>Considerations</h3>
<p>Interfaces are great but can be restrictive, becoming a real pain. Our approach works perfectly for Jira and GitHub, but another future integration can require some extra properties, which the current interfaces don't provide. This can be a problem, but we're probably reasonably safe because we're working with a specific set of events and operations (all based on issues). And if changes need to be made in the future, we'll find a way to make it work for all our integrations.</p>
<h2>Closing</h2>
<p>We're almost ready to release this. We've already implemented the Jira integration into this new system and moved the existing GitHub integration. Now we're in the phase of documenting it all and making sure the fronted code works and looks great!</p>
<p>As a bonus, I'm delighted to announce another new feature to Flare. We wanted to add another integration since we wanted to test how waterproof this whole system was. We managed to add GitLab within two days to Flare. So, we'll launch our complete integration revamp soon with Jira, GitHub, and GitLab.</p>
<p>Are there any other integrations with issue trackers you're interested in? Make an issue on our <a href="https://github.com/spatie/flareapp.io-roadmap/discussions">roadmap</a>.</p>
]]>
            </summary>
                                    <updated>2023-09-27T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Why error tracking is crucial for your application]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/why-error-tracking-is-crucial-for-your-application" />
            <id>https://flareapp.io/why-error-tracking-is-crucial-for-your-application</id>
            <author>
                <name><![CDATA[Christoph]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Errors will inevitably occur in your application. There is no way around it. If you notice them while working locally, you can fix them immediately. However, it gets more tricky if your application is already live. In production, you are no longer the only person triggering errors and the most crucial errors might go unnoticed. This is where error tracking software like Flare comes in handy.</p>
<h2>Why</h2>
<p>Before utilizing a new tool, it's always important to ask yourself, &quot;Why do I need this?&quot;. So, first, let's discuss the significance of error tracking.</p>
<p><img src="https://content.spatie.be/assets/flare/why-error-tracking-is-crucial-for-your-application/wa0fcxschotumfdmpcrcwf3kheehffxu3g4bnjez.png" alt="" /></p>
<p>When an error occurs in production, you will notice the errors in your log files, or a user will tell you about it, right? So why do you need another tool for that? There are two main reasons. First, you'll want to know about these issues as soon as possible: what happened, why did it happen, and do you need to fix it immediately? These are questions you'll want answered in the first couple of minutes after something goes wrong. You don't want to wait for your users to report these issues because chances are, they never will.</p>
<p>The best case scenario is that you get notified about an error that occurred, and you're able to fix it and contact the affected user before they contact you. As a user, I would love that, and it's only possible if you know about your errors and their context sooner rather than later.</p>
<p>That's why we need bug trackers. A bug tracker will tell you about the error no matter who triggers it. It will also show you an overview of occurred errors and more information about them that will help you fix them. Having all production errors displayed in a user-friendly UI makes them easier to handle than searching through log files or depending on users.</p>
<h3>A real life example</h3>
<p>Christoph sells his online courses over a little custom platform. One day, a customer gets in touch about an issue while trying to purchase one of his courses. It turns out Christoph's payment provider changed their webhook calls, which led to an error that went unnoticed.</p>
<p>Unfortunately, this mystery customer was one of many to experience this issue. There have been several before her who didn't mention or report the problem. Without even knowing it, Christoph lost several interested customers because he wasn't aware of the errors in his checkout flow.</p>
<h2>How</h2>
<h3>Introducing Flare, the error tracking tool for Laravel</h3>
<p>Several excellent bug trackers are already available for PHP and JavaScript. Established solutions like Bugsnag, Sentry, and Rollbar offer reliable error tracking. However, Flare stands out due to its tailored Laravel integration. Let's explore some features that make Flare stand out from other bug trackers and discuss how its Laravel integration works wonders for Artisans like you.</p>
<p>Let's start with your <strong>local development environment</strong>. Other error trackers typically only report production errors. However, Flare's PHP client has a beautiful error page to make local development much more enjoyable.</p>
<p><img src="https://content.spatie.be/assets/flare/why-error-tracking-is-crucial-for-your-application/s9fpk8fu7mbakeyahcsmumugrhh6re1ccceft3jj.png" alt="" /></p>
<p>Apart from displaying the exception, error message, and stacktrace in a clean and modern UI, it's also packed with additional context. Executed SQL queries and timings are listed, request parameters are revealed, and view data is displayed. Additionally, Ignition has a free-forever share feature to share an error with colleagues in Slack or GitHub quickly. It also offers dark mode, editor links, relevant documentation links, and AI solutions.</p>
<p>If you're using Laravel, you might've noticed that Ignition comes pre-installed in the framework as the default error page, making it even easier to configure an API key and start tracking production errors.</p>
<p>You can think of Flare as the Ignition error page in production. In fact, you'll see the Ignition interface is an important part of the Flare UI:</p>
<p><img src="https://content.spatie.be/assets/flare/why-error-tracking-is-crucial-for-your-application/60w32qhxh74o70ubfudx6pqxmtdfoaxt4rhrtemj.png" alt="" /></p>
<p>Flare takes error handling to the next level. It doesn't just list all reported errors across all your users, but it also provides extensive insights necessary to debug problems. Aside from error tracking, Flare also has a great GitHub integration, extensive notification settings, and project and team organizing options.</p>
<h3>Getting Started with Flare</h3>
<p>Enabling Flare in a Laravel project couldn't be more effortless. It's a 3-step process, and you can get started with a 10-day trial for free. There is no need to provide credit card details.</p>
<p>Once you have an account, let's create a new project. A project holds all the errors for one application's environment. Please give it a name and select the appropriate environment and technology. Next, you will see instructions on how to connect your application to this new project:</p>
<p><img src="https://content.spatie.be/assets/flare/why-error-tracking-is-crucial-for-your-application/fcd7qtkc8pnjkdi1vca644wm1moer8wvob4pcbln.jpg" alt="" /></p>
<p>It boils down to:</p>
<ol>
<li>
<p>Install Ignition, the Flare client, and pretty error page</p>
</li>
<li>
<p>Configure your API key</p>
</li>
<li>
<p>Configure Laravel to send errors to Flare</p>
</li>
</ol>
<p>Afterward, you can use the &quot;php artisan flare:test&quot; command to test the integration. If set up correctly, it will send a test exception to Flare, which will show up in your project seconds later.</p>
<p>Congratulations, you are now tracking your application's errors. This was the most critical step toward a better error-handling future.</p>
<h3>Developer Experience</h3>
<p>Design and UI are essential to give your users a great experience. In the same way, good documentation and a well-coded client package are essential to give developers a great developer experience (DX).</p>
<p>Fortunately, the Spatie team working on Flare has several years and 300 open-source projects of experience creating software with fantastic developer experience.</p>
<p>Another example of good DX are the various filters and insights available for errors. Every time we use Flare to debug a problem, we look for insights or filters that would make the process easier:</p>
<p><img src="https://content.spatie.be/assets/flare/why-error-tracking-is-crucial-for-your-application/6xbfs8gauzfqph5j5a1ksixbiqtopyjyiitipxph.png" alt="" /></p>
<h3>Introducing solutions</h3>
<p>Ignition and Flare can recognize and fix several commonly occurring errors. If you've ever lost an hour debugging a missing semi-colon or a typo, you'll be glad to learn Flare will automatically pick up on this and suggest a fix. When debugging locally using Ignition, you can even execute these solutions directly from the error page and solve the issue without ever leaving the browser.</p>
<p>Your browser does not support the video tag.</p>
<p>Recently, we've also added opt-in AI solutions using OpenAI's APIs. Every Flare subscription comes with a free daily quota of AI solutions. The future is now 🤖.</p>
<p><img src="https://content.spatie.be/assets/flare/why-error-tracking-is-crucial-for-your-application/r67nfjypj97qjdbhzseuslvog8i2cqcbn1gr9dxc.png" alt="" /></p>
<h3>GitHub integration</h3>
<p>Flare includes a stunning GitHub integration. Once enabled, you can link a Flare project to a GitHub repository. This way, you can link incoming errors to GitHub issues and have the issue automatically close when the error is resolved.</p>
<p><img src="https://content.spatie.be/assets/flare/why-error-tracking-is-crucial-for-your-application/c1xqerii71uxdcc0heetxtici7siqlggfjtf7fb1.png" alt="" /></p>
<p>The GitHub integration also works the other way around. If a Flare URL is detected in a GitHub issue, we'll automatically link the issue to the relevant Flare error. If you work with GitHub a lot, this greatly enhances your workflow and opens up new possibilities for project management.</p>
<p>Do you love project management, but don't like GitHub? We're also working on a Jira integration, which should land in the coming few weeks.</p>
<h3>Identifying users</h3>
<p>When a severe error occurs in your application, you might want to notify the affected users proactively. However, it is usually challenging to identify the affected users from endless log files or traditional error trackers. That's why Flare has you covered by automatically detecting the logged-in user and providing exportable insights about affected users for each error.</p>
<p><img src="https://content.spatie.be/assets/flare/why-error-tracking-is-crucial-for-your-application/l7vi9uvancnwg074i7vrfnlozdyhynrat5nsw5oc.png" alt="" /></p>
<p>Additionally, the insight of affected users makes it even easier to find a user to impersonate locally to debug the problem.</p>
<h3>Advanced notifications settings</h3>
<p>We already mentioned how important it is to be notified of new errors in your application. Flare has an extensive notification system that lets you customize notifications to your personal needs and the needs of your team.</p>
<p>First, it provides several different notification channels out of the box: mail, Slack, SMS, Telegram, Discord, and Microsoft Teams. Just pick what suits you best. Need something specific? The webhook channel allows for endless customizability.</p>
<p>For each channel, you can customize which notifications you want to receive. For example, it's easy to configure a global notification channel to send a notification about the first occurrence of every error to a Slack channel. But if this error happens the tenth time, you may need to get someone out of bed by texting them.</p>
<p><img src="https://content.spatie.be/assets/flare/why-error-tracking-is-crucial-for-your-application/yri8iv1r77lmewdljz2ui4ohkrucwrgoxncxjhm1.png" alt="" /></p>
<p>Customizing notifications to your personal or your team's needs is vital. Having advanced options lets you ensure no one misses a notification for that critical project and no one gets overwhelmed with notification overload.</p>
<h2>Conclusion</h2>
<p>Bug tracking is a crucial part of building and maintaining any application. Not getting notified about occurring errors can cost you a lot of time and money. Flare is here to help. You don't have to worry about possible problems anymore because Flare will notify you immediately, and its specific focus on Laravel will make debugging even more straightforward.</p>
<p>It is never too late to start tracking errors. <a href="https://flareapp.io/register">Give Flare a try now</a>.</p>
]]>
            </summary>
                                    <updated>2023-09-11T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing the Flare affiliate program]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-the-flare-affiliate-program" />
            <id>https://flareapp.io/introducing-the-flare-affiliate-program</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>We're proud to announce that we have started <a href="https://flareapp.io/affiliate-program">our affiliate program</a></p>
<p>Using this program, you can generate a link (like <code>https://flareapp.io?via=your-name</code>) that you can include in your blog posts, tweets, or anywhere on the web. If somebody clicks that link and subscribes to Flare in the next 30 days, you'll get 20% of all revenue the new user generates. So to be clear, you'll get a cut for as long as new user has a subscription. Sweet!</p>
<p>And of course, The more people subscribe via your link, the more money you earn.</p>
<p>After you've created your affiliate account, you'll get access to a beautiful dashboard where you can track your visits and earnings.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-the-flare-affiliate-program/yqsynwyxusfxjoc3jvozk4t14pbukcqrwnnuaxtd.jpg" alt="" /></p>
<p>There, you can also download logos and banners that you can use to promote Flare.</p>
<p>You'll find more info on all of this on <a href="https://flareapp.io/affiliate-program">our brand new affiliate program page</a>.</p>
]]>
            </summary>
                                    <updated>2023-08-24T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[New teams will now see an example project]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/new-teams-will-now-see-an-example-project" />
            <id>https://flareapp.io/new-teams-will-now-see-an-example-project</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When people first create their Flare account (or create a new team within their account), they are greeted with instructions on how to send a first error to Flare. They would only see what an error in Flare looked like once they'd sent one themselves.</p>
<p>For people new to Flare, we wanted to make it easier to immediately see what errors look like and showcase some of the cool things we do when rendering particular errors. That's why new accounts and teams will now see an example project added to their account.</p>
<p><img src="https://content.spatie.be/assets/flare/new-teams-will-now-see-an-example-project/iogjhbggx8bmz9cjhir2x9odyor7jppsgppyjur6.jpg" alt="" /></p>
<p>This project contains some nice example errors, like this one where we have code highlighting for the SQL query.</p>
<p><img src="https://content.spatie.be/assets/flare/new-teams-will-now-see-an-example-project/rz0tv8veibdpbfjvyzp1pgik0bdd37bj79uqrsxv.jpg" alt="" /></p>
<p>There's also an example error where we show the list of URLs where it occurred (all fictional URLs for this example).</p>
<p><img src="https://content.spatie.be/assets/flare/new-teams-will-now-see-an-example-project/6aaftliljdynjntdcg5s3sqyuqntocio1vveq9ka.jpg" alt="" /></p>
<p>And, of course, we also show an error that uses one of our defining features: solutions.</p>
<p><img src="https://content.spatie.be/assets/flare/new-teams-will-now-see-an-example-project/iqjhey0js9aprilyshezx0mfcrc9fie9h7ypvz97.jpg" alt="" /></p>
<p>We hope this example project makes it easier for people to see that Flare can beautifully display all errors.</p>
]]>
            </summary>
                                    <updated>2023-08-24T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Unveiling error notes and the activity feed]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/unveiling-error-notes-and-the-activity-feed" />
            <id>https://flareapp.io/unveiling-error-notes-and-the-activity-feed</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Today we're introducing the error activity feed. It shows you what happened with an error during its lifetime within Flare. When it was resolved, snoozed or reoccurred.</p>
<p><img src="https://content.spatie.be/assets/flare/unveiling-error-notes-and-the-activity-feed/gbapnruxsjlve3wacdchmlvzs9274bmw3be6huoi.png" alt="" /></p>
<p>As developers, we know that two heads are better than one, especially when it comes to resolving complex issues. Enter Flare notes your secret weapon for seamless collaboration during error resolution.</p>
<p>On every error in Flare you can now leave notes directly. Sharing insights and hypotheses with your team in real-time. Team members can jump in, add their thoughts, and engage in discussions effortlessly.</p>
<p>You'll find the new notes section by clicking &quot;show activity&quot; on an error.</p>
<p><img src="https://content.spatie.be/assets/flare/unveiling-error-notes-and-the-activity-feed/alx3v5t69nznbsnonnudniahdhegmb8yc9w9kewl.png" alt="" /></p>
<p>From now on when you're executing an action on an error (resolving, unresolving, snoozing and unsnoozing) then Flare will show you a new dialog where you can now leave a note behind with the reason for executing that action.</p>
<p><img src="https://content.spatie.be/assets/flare/unveiling-error-notes-and-the-activity-feed/p87hixyd8ajmcyni5mthqvydlzlqq0ww30ydamos.png" alt="" /></p>
<p>These notes will all be shown on the activity feed which will provide more information on the why certains actions happened.</p>
<p>Happy error hunting!</p>
]]>
            </summary>
                                    <updated>2023-08-11T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Web animation wizardry]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/web-animation-wizardry" />
            <id>https://flareapp.io/web-animation-wizardry</id>
            <author>
                <name><![CDATA[Sam]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Picture this: a bunch of developers, most of them back-enders, are given a Figma design file to turn into the <a href="https://www.flareapp.io">new homepage for Flare</a>. We set out to build our brand-new marketing site with a sprinkle of animations. Here is how we crafted them.</p>
<blockquote>
<p>If you have not seen the new homepage, check it out before reading this. Try scrolling slowly and quickly to catch all of the little details. It's worth it, I promise.</p>
</blockquote>
<h2>Sticky navbar</h2>
<p>The design files had a different state for the navigation menu for when the page is not scrolled yet and when it is scrolled. To transition between them, we used a custom hook to determine when the page is scrolled to transition the width of a wrapper div from 100% to 0%. To prevent the menu from actually going to 0% we set a <code>min-width: max-content</code>, which scales down the container to the size of the actual content.<br />
– Don't you love CSS?</p>
<h2>Error card carousel</h2>
<p>We went through a lot of iterations on this one, but I feel like we landed on a nice and easy 3-step solution.</p>
<h3>1. Positioning</h3>
<p>Each card is positioned at the same position by using a single-cell grid layout. This prevents layout shifts because the wrapper will be sized based on the largest card.</p>
<p>Tailwind classed to achieve a single-cell grid layout:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">div </span><span style="color: #81A1C1">class=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">grid grid-cols-1 grid-rows-1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">article </span><span style="color: #81A1C1">class=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">row-start-1 col-start-1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF">Card </span><span style="color: #B48EAD">1</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">article</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">article </span><span style="color: #81A1C1">class=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">row-start-1 col-start-1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF">Card </span><span style="color: #B48EAD">2</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">article</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;!--</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">--&gt;</span></span>
<span class="line"><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">div</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"></span></code></pre>
<h3>2. Offset</h3>
<p>CSS variables are used to make the offsets configurable.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cardstack </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">card</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">offset</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF">rem</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">scale</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">factor</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">07</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">opacity</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">factor</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">23</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Each card is scaled and translated based on its index in the stack.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cardstack </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> article </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  will</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">change</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> transform</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> opacity</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  transform: </span><span style="color: #88C0D0">translateY</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">calc</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">var</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">--</span><span style="color: #88C0D0">index</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">*</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">*</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">var</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">--</span><span style="color: #88C0D0">card</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">offset</span><span style="color: #ECEFF4">)))</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">scale</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">calc</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">-</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">var</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">--</span><span style="color: #88C0D0">index</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">*</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">var</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">--</span><span style="color: #88C0D0">scale</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">factor</span><span style="color: #ECEFF4">)))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  opacity: </span><span style="color: #88C0D0">calc</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">-</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">var</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">--</span><span style="color: #88C0D0">index</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">*</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">var</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">--</span><span style="color: #88C0D0">opacity</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">factor</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  transition: transform </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">5</span><span style="color: #D8DEE9FF">s ease</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">in</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">out</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> opacity </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">5</span><span style="color: #D8DEE9FF">s ease</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">in</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">out</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The index can be tracked through the <code>:nth-child()</code> selector, combined with a preprocessor like sass this can be done with a simple for loop.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">@for</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> from </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> through </span><span style="color: #B48EAD">5</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cardstack </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> article</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">nth</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">child</span><span style="color: #ECEFF4">(</span><span style="color: #616E88">#{6 - $i}) {</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #81A1C1">--</span><span style="color: #88C0D0">index</span><span style="color: #81A1C1">:</span><span style="color: #88C0D0"> </span><span style="color: #616E88">#{$i};</span></span>
<span class="line"><span style="color: #88C0D0">  }</span></span>
<span class="line"><span style="color: #88C0D0">}</span></span>
<span class="line"></span></code></pre>
<p>Alternatively, you can write it out by hand:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cardstack </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> article</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">nth</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">child</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">index</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">5</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">/* backmost card */</span></span>
<span class="line"><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cardstack </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> article</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">nth</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">child</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">index</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cardstack </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> article</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">nth</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">child</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">index</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cardstack </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> article</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">nth</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">child</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">4</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">index</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cardstack </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> article</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">nth</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">child</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">5</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">index</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cardstack </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> article</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">nth</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">child</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">6</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">index</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">/* topmost card */</span></span>
<span class="line"></span></code></pre>
<h3>3. Rotate the stack</h3>
<p>The topmost card (last in the list) is added to the back of the stack (top of the list). The trick here is to make the React component key follow each card. This prevents the cards from unmounting and allows the cards to transition smoothly.</p>
<h2>Epic screenshot</h2>
<p>Arguably the most epic transition on this page is the screenshot reveal.<br />
This animation can be broken down into four steps.</p>
<h3>1. Track the scroll position</h3>
<p><a href="https://www.framer.com/motion/">Framer-motion</a> provides a <code>useScroll</code> hook that can track the vertical scroll progress of the screenshot in the viewport. Setting the <code>offset</code> option to <code>[&quot;start start&quot;, &quot;start end&quot;]</code> maps the scroll position from the start of entering the viewport to when the element is entirely inside of the viewport.</p>
<h3>2. Smoothly map the progress to transformations</h3>
<p>Framer Motion handles binding the translation and rotation of the element to the vertical scroll progress from step 1.<br />
First, we use the <code>useTransform</code> hook to map the vertical scroll progress from an input range to an output range. Then the <code>useSpring</code> hook handles smoothly snapping the values to the actual scroll position.</p>
<h3>3. Transform in 3D</h3>
<p>To achieve the final effect, we set the css <code>perspective</code> property to move the user (you) away from the z0 plane to give the 3D-positioned element perspective. The transform origin is set to the bottom of the element to make it smoothly transition from moving towards z0. Now the screenshot is ready for z-translation and x-rotation.</p>
<h3>4. Blur</h3>
<p>The same Framer Motion hooks bind the blur value to the element. The input range is shortened to make it readable earlier in the animation.</p>
<h2>Retrospective</h2>
<p>Getting these animations to perform well for a marketing site was challenging. On our way, we ran into layout shifts, browser inconsistencies regarding positioning and scroll tracking, performance problems due to background blurs and complex SVGs, and many more papercuts that slowed us down.<br />
We hoped Framer Motion would be a drop-in fix for all of these problems, but using regular CSS ended up being easier to develop and debug.<br />
Ultimately, we came up with solutions for these challenges and learned a lot. I hope you learned something too.</p>
]]>
            </summary>
                                    <updated>2023-07-31T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[You can now log in using your Google or GitHub account]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/you-can-now-log-in-using-your-google-or-github-account" />
            <id>https://flareapp.io/you-can-now-log-in-using-your-google-or-github-account</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>You can now register and log in to Flare using your Google or GitHub account.</p>
<p>You'll see these two new buttons on the register and login page.</p>
<p><img src="https://content.spatie.be/assets/flare/you-can-now-log-in-using-your-google-or-github-account/nj0tuu3mkrgv4psxcc6wx89lwvx2maobfmjowwph.jpg" alt="" /></p>
<p>You can log in to Flare using these buttons with a single click. This is way faster than the traditional email / password flow (where we'll also need to verify your email).</p>
<p>In this blog post, I'd like to show you how this feature works behind the scenes. Spoiler: <a href="https://laravel.com/docs/10.x/socialite">Socialite</a> makes this a breeze.</p>
<h2>Implementing social login in a Laravel app</h2>
<p>Google and GitHub offer login functionalities through what is called the OAuth flow. This flow will redirect the user from Flare to a login page on Google/GitHub, and after the user has logged in there, Google/GitHub will redirect the user back to Flare, which contains information about the logged-in user. That's the simplified version.</p>
<p><img src="https://content.spatie.be/assets/flare/you-can-now-log-in-using-your-google-or-github-account/xlf0h3hwytk3e2lxvit18l4cpckgjixijmipgubn.jpg" alt="" /></p>
<p>Behind the scenes, Flare is a Laravel app. Let's review the steps we took to add a social login to Flare.</p>
<p>Both Google and GitHub will send ids and tokens around the user the will be logged in. We added some columns in our database to hold that info.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Database</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Migrations</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Migration</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Database</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Schema</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Blueprint</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Schema</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">class</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Migration</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">up</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Schema</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">table</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">users</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Blueprint</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">after</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">remember_token</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Blueprint</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">string</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">github_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">nullable</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">index</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">string</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">google_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">nullable</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">index</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Laravel has a great first-party package called <a href="https://laravel.com/docs/10.x/socialite">Socialite</a> that can handle logins using the OAuth flow.</p>
<p>You can install it using Composer:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">composer </span><span style="color: #81A1C1">require</span><span style="color: #D8DEE9FF"> laravel</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">socialite</span></span>
<span class="line"></span></code></pre>
<p>Next up, you should create an OAuth application at Google and/or GitHub that will provide you with a <code>client_id</code> and <code>client_secret.</code> Explaining how you can create this OAuth application is beyond the scope of this post, but you'll probably find it quickly with some Googling.</p>
<p>In the <code>config/services.php</code> file, we added these keys.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">		</span><span style="color: #616E88">// other values...	</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">github</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">client_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">GITHUB_CLIENT_ID</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">client_secret</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">GITHUB_CLIENT_SECRET</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">redirect</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">APP_URL</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">/auth/callback/github</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">],</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">google</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">client_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">GOOGLE_CLIENT_ID</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">client_secret</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">GOOGLE_CLIENT_SECRET</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">redirect</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">APP_URL</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">/auth/callback/google</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Of course, you should also set the client id and secret as <code>.env</code> variables; use the names mentioned in the config file above.</p>
<p>Next, let's add two routes to handle the social login.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Auth</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">SocialiteController</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// in a routes file</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Route</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">auth/redirect/{driver}</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #8FBCBB">SocialiteController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">redirect</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">name</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">socialite</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #8FBCBB">Route</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">auth/callback/{driver}</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #8FBCBB">SocialiteController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">callback</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>When a user visits the first route, the user will be redirected to Google/GitHub. The second route will be hit when Google/GitHub redirects back to Flare. If the request contains the correct information, we'll log in to the user.</p>
<p>Let's take a look at the implementation of the<br />
<code>SocialiteController</code> that handles requests to both these routes.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Auth</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">FindOrCreateUserForSocialiteAction</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Enums</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">SocialiteDriver</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">RedirectResponse</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Auth</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Laravel</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Socialite</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Socialite</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Laravel</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Socialite</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Two</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">InvalidStateException</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SocialiteController</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">redirect</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RedirectResponse</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">abort_if</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #88C0D0">in_array</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">github</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">google</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]),</span><span style="color: #88C0D0"> </span><span style="color: #B48EAD">404</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Laravel</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Socialite</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Two</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">GoogleProvider</span><span style="color: #ECEFF4">|\</span><span style="color: #616E88">Laravel</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Socialite</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Two</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">GithubProvider</span><span style="color: #616E88"> $socialite */</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">socialite</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Socialite</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">driver</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">match</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">github</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">socialite</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">scopes</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">read:user</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">user:email</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">redirect</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">google</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">socialite</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">redirect</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">callback</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RedirectResponse</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	     </span><span style="color: #616E88">// implementation removed for brevity...</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>So in the <code>redirect</code> function of the controller, we will redirect to Google/GitHub. Socialite will build up the correct URL. Nice!</p>
<p>Next, let's look at the implementation of <code>callback</code>, which will be called when Google/GitHub redirects back.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;?</span><span style="color: #D8DEE9FF">php</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Front</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Controllers</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CreateUserAction</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Database</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Eloquent</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Builder</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">RedirectResponse</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Auth</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Str</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Laravel</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Socialite</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Contracts</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">User</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> SocialiteUser</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Laravel</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Socialite</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Socialite</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SocialiteController</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">redirect</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RedirectResponse</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">				</span><span style="color: #616E88">// removed for brevity        </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">callback</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RedirectResponse</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">abort_if</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #88C0D0"> in_array</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">github</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">google</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]),</span><span style="color: #88C0D0"> </span><span style="color: #B48EAD">404</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">socialiteUser</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Socialite</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">driver</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">user</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">findUserByKeyOrEmail</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">socialiteUser</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">createUser</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">socialiteUser</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">updateUser</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">socialiteUser</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Auth</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">login</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> remember</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">redirect</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">route</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">projects.index</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// other functions removed for brevity, well get </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// to those.</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"></span></code></pre>
<p>In the above code, you see that we're going to get to the information that Google/GitHub provided by calling <code>Socialite::driver($driver)-&gt;user()</code>. This object contains the email and 3rd party user id of the user at the external service. We will use that information to look up the user with the same email or 3rd party user id in our local data pass. If such a user doesn't exist, well create it. After that, we'll log in the user and redirect to the <code>projects.index</code> route where all Flare projects for that user are shown.</p>
<p>To finish off, let's look at the implementations of the <code>createUser</code> and <code>updateUser</code> functions.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;?</span><span style="color: #D8DEE9FF">php</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Front</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Controllers</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CreateUserAction</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Database</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Eloquent</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Builder</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">RedirectResponse</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Auth</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Str</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Laravel</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Socialite</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Contracts</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">User</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> SocialiteUser</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Laravel</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Socialite</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Socialite</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SocialiteController</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// other functions</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">findUserByKeyOrEmail</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SocialiteUser</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">User</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">driver</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">_id</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getId</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">orWhere</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">email</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getEmail</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">whereNotNull</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">email_verified_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">})</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">first</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">createUser</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SocialiteUser</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">User</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">CreateUserAction</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            name</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getName</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            email</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getEmail</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            password</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Str</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">random</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">64</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            attributes</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">driver</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">_id</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getId</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">email_verified_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">updateUser</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">driver</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">User</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SocialiteUser</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">socialiteUser</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">fill</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">driver</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">_id</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">socialiteUser</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getId</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">email_verified_at</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">email</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">socialiteUser</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getEmail</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">email_verified_at</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">save</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The above code should be pretty straightforward.</p>
<h2>In closing</h2>
<p>In this post, you learned that adding a social login in a Laravel app is pretty easy. <a href="https://laravel.com/docs/10.x/socialite">Socialite</a> does the heavy lifting for you.</p>
<p>In our sister service, Oh Dear, we see that about half of the log ins and registrations now happen via Google. People seem to like that they can now log in using a single click. Adding this to your app might be worthwhile as well.</p>
]]>
            </summary>
                                    <updated>2023-07-24T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Managing production environment variables for Laravel deployments]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/managing-production-environment-variables-for-laravel-deployments" />
            <id>https://flareapp.io/managing-production-environment-variables-for-laravel-deployments</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>&quot;Secret secrets are no fun, secret secrets hurt someone&quot; unless managed right in your Laravel deployment! For Flare, we explored alternative solutions to the classic <code>.env</code> files deployed with every Laravel application. We looked at deploying the <code>.env</code> file using Ansible, using config servers, the <code>env:encrypt</code> command, and eventually <a href="https://sobolevn.me/git-secret/">git-secret</a>, to keep our production environment variables safe and sound.</p>
<p>Environment variables and <code>.env</code> files have been widely used to manage Laravel apps' application secrets. However, these approaches come with their own set of challenges. In this blog post, we will explore the limitations of traditional methods and discuss alternative solutions for managing environment variables securely.</p>
<h2>The limitations of .env and why we need something better</h2>
<p>While <code>.env</code> files and real environment variables are commonly used, they present some challenges in terms of security and manageability:</p>
<p>First, the <code>.env</code> file is full of sensitive information. It's something you want to avoid committing to a GitHub repo (not even a private one). In an ideal scenario, it's not stored as a plain text file but kept encrypted and only decrypted in memory where needed.</p>
<p>Additionally, managing access between different team members can also be challenging. Once you share access to a server or the location of the <code>.env</code> file with a new colleague, there's no way back. In an ideal scenario, we'd have a way to view and manage which team members can access each secret.</p>
<p>Another requirement to consider is versioning. A <code>.env</code> file only kept on the server doesn't keep a history, making it impossible to roll back mistakes.</p>
<p>Finally, deploying changes to the <code>.env</code> file on a single server is easy. It's manual labour, but it's a 5-minute job. However, the task becomes more challenging when more servers are involved, or worse, Kubernetes.</p>
<h2>Alternative Solutions</h2>
<p>To overcome these limitations, let's explore a few alternative solutions for securely managing environment variables.</p>
<h3>Solution 1: Using Ansible for syncing <code>.env</code> files</h3>
<p>Ansible is an open-source project that provides a simple way to automate various configuration and infrastructure deployments. For example, it's trivial to sync a configuration file to multiple servers using Ansible. And that's precisely what we used to sync our <code>.env</code> file to all Flare servers simultaneously.</p>
<p>The downside is that this still involves giving people access to the Ansible server, manually updating your application's plain text <code>.env</code> file, and running an Ansible playbook to deploy the updated <code>.env</code> to all servers. While it worked well enough for years, it would be nice to manage and deploy <code>.env</code> secrets closer to home. For example, in the repo itself...</p>
<h3>Solution 2: Laravel's <code>env:encrypt</code></h3>
<p>Starting from Laravel 9, the <code>env:encrypt</code> command can be used to encrypt the <code>.env</code> file using in a way that the encrypted file can be committed to the GitHub repo. The <code>.env.encrypted</code> can be deployed and decrypted to all production servers. Because the encrypted <code>.env</code> file is stored in the GitHub repo, it is also versioned. This approach checks a lot of boxes, but it still has one single drawback: access is managed through a single encryption key that needs to be shared and protected. This means there is no way to revoke access from a single user.</p>
<h3>Solution 3: Secret management products</h3>
<p>The various secret management products are arguably the best alternative for <code>.env</code>. For the big three, we have AWS Parameter Store, Google Secrets Manager, and Azure Key Vault (why do all of these products have different names for the same concept?). Another honorable mention is HashiCorp Vault. These solutions are made specifically for this problem and provide a ton of extra advantages:</p>
<ul>
<li>
<p>Automatic secret rotation: config servers can rotate secrets automatically, reducing the risk of compromised credentials.</p>
</li>
<li>
<p>Granular access control allows you to give specific users access to <em>individual</em> secrets.</p>
</li>
<li>
<p>Integration possibilities: Config servers typically don't write to a <code>.env</code> file. Instead, they'll decrypt the secrets to memory at runtime, avoiding the various risks of a plain text <code>.env</code> file</p>
</li>
<li>
<p>Built-in audition solutions</p>
</li>
<li>
<p>Real-time updates (depending on the application)</p>
</li>
</ul>
<p>While this seems like the ideal solution, it is another service to manage or pay for, and for now, it feels a bit too heavy for our use case.</p>
<h3>Solution 4: git-secret - What we ended up using at Flare</h3>
<p><a href="https://sobolevn.me/git-secret/">git-secret</a> is built to solve the exact problem of managing secrets close to the git repository. Working very similar to Laravel's own <code>env:encrypt</code> command, its most significant advantage is that access is managed through a GPG keyring instead of a single shared encryption key. This means we can give and revoke access to each team member through their public key.</p>
<p>Additionally, it checks all the other boxes:</p>
<ul>
<li>
<p>Secrets are stored directly in the repository, making managing and tracking changes easy.</p>
</li>
<li>
<p>No additional infrastructure or services are required.</p>
</li>
<li>
<p>Built right into existing tooling: We already use git everywhere during development and deployment.</p>
</li>
<li>
<p>Deploys stay super simple: one extra command is all it takes to deploy the encrypted <code>.env</code> file.</p>
</li>
</ul>
<p>Let's switch from theory to practice and look at how we set up git-secret &amp; add the public key for the first user.</p>
<h3>Setting up git-secret</h3>
<ol>
<li>
<p>Install git-secret on your local machine and servers. Because git-secret is an extension to the Git CLI, it must be installed separately. Installation instructions can be found <a href="https://sobolevn.me/git-secret/installation">here</a>.</p>
</li>
<li>
<p>Create a new GPG key pair using the <code>gpg --gen-key</code> command.</p>
</li>
<li>
<p>Export the public key for your newly created GPG key pair: <code>gpg --armor --export your.email@address.com &gt; public-key.gpg</code></p>
</li>
<li>
<p>Initialize git-secret by running the <code>git secret init</code> command in your app's repository. The relevant sensitive files will automatically be added to your <code>.gitignore</code>, but checking either way is a good idea.</p>
</li>
<li>
<p>Use <code>git secret add .env.production</code> to add the <code>.env.production</code> to this repo's list of encrypted files. Again, the relevant sensitive files will automatically be added to your <code>.gitignore</code>.</p>
</li>
<li>
<p>Run <code>git secret hide -d' to encrypt the </code>.env<code>file. Because the</code>-d' flag was used, the original file will also be deleted.</p>
</li>
<li>
<p>Share the secret with the user's newly created public key using <code>git secret tell &lt;you.email@address.com&gt;</code></p>
</li>
<li>
<p>You can now test decrypting the encrypted secret using <code>git secret reveal</code>. If all goes well, the <code>.env.production</code> file should show back up.</p>
</li>
</ol>
<p>After you're ready to deploy your application to production, <code>git secret hide -d</code>, commit, and deploy to production. On the production server (or as the last step in the deploy script), we can now decrypt the <code>.env</code> file using the same <code>git secret reveal</code> command.</p>
<p>Like any good tool, many extra features, options, and configurations exist. You can read about it in the <a href="https://sobolevn.me/git-secret/#commands">git-secret docs</a>.</p>
<p><strong>Some notes on running git-secret in production</strong></p>
<p>The encrypted secrets are binary files. This makes it impossible (or annoyingly hard) to merge them in PRs or other branching actions. That's why it's essential to only commit changes to the encrypted <code>.env</code> files on a single branch and to push them to the remote repo immediately. This way, we can avoid all merge conflicts with these binary files.</p>
<p>Another good tip is to set up a separate Git repository to manage the public keys of your team. Using <code>gpg --import &lt;key&gt;</code>, you can quickly import these keys into your GPG keychain when necessary.</p>
<h3>Conclusion</h3>
<p>Managing environment variables securely is crucial for Laravel apps. While traditional methods like .env files have limitations, alternative solutions provide better security and manageability. This post discussed solutions like the various secret management products, Laravel's own <code>env:encrypt</code> command, Ansible, and git-secret.</p>
<p>For Flare and our team, git-secret emerged as a comprehensive solution, offering user-specific access control and seamless integration in our existing development and deployment flow.</p>
]]>
            </summary>
                                    <updated>2023-07-17T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[PHP stack trace arguments have landed in Flare]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/php-stack-trace-arguments-have-landed-in-flare" />
            <id>https://flareapp.io/php-stack-trace-arguments-have-landed-in-flare</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Since Flare's beginning, we've shown you stack traces with each error you've sent us. These stack traces are exact copies of the traces generated by Ignition, the error page we ship for Laravel and generic PHP projects:</p>
<p>This week we've rolled out a new feature: stack trace arguments, and I'll know for sure you'll love them!</p>
<p>The stack trace shows each method/function called from the start of your application until an exception is thrown. A frame with a file of the method/function, line number, code snippet, and arguments represents each method/function call. Until this week, we didn't show you these arguments. You can read more about why we did this <a href="https://flareapp.io/blog/57-sensitive-parameters-in-php-82">here</a>.</p>
<p>That all changes when you now update to the newest Ignition client. We already had a stack trace:</p>
<p><img src="https://content.spatie.be/assets/flare/php-stack-trace-arguments-have-landed-in-flare/ncer6o1vmhbijkodyaptiamcozr6iqzeetlhwfns.png" alt="" /></p>
<p>When clicking through the stack frames you'll see these arguments popping up, they represent the arguments passed to the method/function the frame is currently highlighting.</p>
<p><img src="https://content.spatie.be/assets/flare/php-stack-trace-arguments-have-landed-in-flare/9xss43wpnqfhue8tha5ljmtnyp8xvkmr25ctp9zd.png" alt="" /></p>
<p>We're smart with these arguments and will try to convert them to more readable objects. Read on for more information about this.</p>
<p><img src="https://content.spatie.be/assets/flare/php-stack-trace-arguments-have-landed-in-flare/ovbyuj9pgvqk0zbjy6td48wmww25l6j3p5g0wjkl.png" alt="" /></p>
<p>And, of course, you'll be able to see these arguments within Flare! From now on, you'll know which values have been passed around within your code.</p>
<p><img src="https://content.spatie.be/assets/flare/php-stack-trace-arguments-have-landed-in-flare/rstsjjish6p83itepbustfh3h41odbcuelmmsrjd.png" alt="" /></p>
<p>Are you interested in the technical implementation? Keep on reading!</p>
<h2>How we did it</h2>
<p>To get this all working, we needed to update a whopping seven repositories! The most important one was <a href="https://github.com/spatie/backtrace">spatie/backtrace</a>. This is the heart of Ignition. It uses PHP-generated stack traces and creates an optimized, more readable version from it.</p>
<p>In the past, we already had support for adding arguments to stack traces using the package. But this was turned off for Ignition and Flare. So let's enable it:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #8FBCBB">Backtrace</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">createForThrowable</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">withArguments</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">frames</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Cool, that's it. Now we're done, books closed ... That's not how we roll at Spatie. Let's see how we can make this even better.</p>
<p>First of all, to which frame do these arguments belong? Let's take a look at the following example:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__invoke</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">thisIsTheWay</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">mandalorianName</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vehicle</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">takesOffHelmet</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...$</span><span style="color: #D8DEE9">children</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">GalaxyPerson</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vehicle</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">mandalorianName</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">takesOffHelmet</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">...$</span><span style="color: #D8DEE9">children</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">person</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">thisIsTheWay</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Din Djarin</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Razor Crest</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Grogu</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The create method does not exist, so that an exception will be thrown. On which stack frame would you expect the arguments? Here:</p>
<p><img src="https://content.spatie.be/assets/flare/php-stack-trace-arguments-have-landed-in-flare/5ebfioxsswzck7forvokuzwuk9uteqhxmqgmumja.png" alt="" /></p>
<p>Or here?</p>
<p><img src="https://content.spatie.be/assets/flare/php-stack-trace-arguments-have-landed-in-flare/mwoisi3afe5secnl2dyzmyuvbnwq214qrjwl803v.png" alt="" /></p>
<p>As you can see, we opted for the second frame. This might feel strange in the beginning. Isn't it handy to see the current arguments in the code they are used in?</p>
<p>We have a few reasons for this:</p>
<ul>
<li>
<p>You'll see the arguments used to make the function call</p>
</li>
<li>
<p>When you have a giant function, the code snippet might be too small to quickly see what's happening to the arguments making the feature rather useless</p>
</li>
<li>
<p>It's PHP's default</p>
</li>
</ul>
<p>Now that we know that, let's look at how we're parsing these arguments.</p>
<h3>Parsing PHP arguments</h3>
<p>Let's use the example above and look at the frame we're receiving from PHP:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">file</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/Users/ruben/Spatie/star-wars/app/Http/Controllers/DataController.php</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">line</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">23</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">function</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">App\Http\Controllers</span><span style="color: #EBCB8B">\t</span><span style="color: #A3BE8C">hisIsTheWay</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">args</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">▼</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Din Djarin</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Razor Crest</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>How do we transform this information into this?</p>
<p><img src="https://content.spatie.be/assets/flare/php-stack-trace-arguments-have-landed-in-flare/oczbzmq5rvfjv0pgahjcbgl9tszyc2q1ghy78q2j.png" alt="" /></p>
<h3>Compatibility</h3>
<p>We're now going through the code. If you're asking yourself, why not use this fancy PHP 8.* feature? We want Flare and Ignition to be installable in as many projects as possible, even the older ones. Our code is written in PHP 7.3 to support these older projects. The downside is that we're missing out on some excellent new features.</p>
<h3>The need for reflection</h3>
<p>First, we need the names of these parameters. We're nothing with parameter names 0, 1, and 2. Reflection to the rescue:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getParameters</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">method</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?array</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reflection</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!==</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ReflectionMethod</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">method</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ReflectionFunction</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">method</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ReflectionException</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">e</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">array_map</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #81A1C1">function</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ReflectionParameter</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reflectionParameter</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #88C0D0">            </span><span style="color: #81A1C1">return</span><span style="color: #88C0D0"> </span><span style="color: #8FBCBB">ProvidedArgument</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">fromReflectionParameter</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reflectionParameter</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">reflection</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getParameters</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>So what happens here? We create a reflection object based upon that a frame is a method or function call. And then, we'll retrieve all parameters for this method/function and transform them to our structure: <code>ProvidedArgument</code>. The code for this looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">static</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fromReflectionParameter</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ReflectionParameter</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getName</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isPassedByReference</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isVariadic</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isDefaultValueAvailable</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isDefaultValueAvailable</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parameter</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getDefaultValue</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>So we keep for each parameter the name, if it is passed by reference, if it is variadic (these are the ... kinds of arguments), if it has a default value, and what that default value is.</p>
<p>Why keep track of the default value? Let's take a look at a PHP frame for this method call:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">thisIsTheWay</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #88C0D0">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Din Djarin</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Razor Crest</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">//Notice how we&#39;re missing $takesHelmetOff, which is a default value</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>The PHP frame now looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">file</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/Users/ruben/Spatie/star-wars/app/Http/Controllers/DataController.php</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">line</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">34</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">function</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">App\Http\Controllers</span><span style="color: #EBCB8B">\t</span><span style="color: #A3BE8C">hisIsTheWay</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">args</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">▼</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Bo-Katan Kryze</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Gauntlet Fighter</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>This value is <code>true</code> by default, but we're missing it. That's why we take notice of the value when we're reflecting the function.</p>
<h3>Reducing the payload</h3>
<p>Now that we have our <code>ProvidedArguments</code>, let's look at the values provided. Strings, ints, booleans, floats, and arrays until a certain point are easily readable, but what if you have complex objects?</p>
<p>The quickest fix would be to show the arguments' values if they're simple types and replace complex objects with an &quot;object&quot; value.</p>
<p>But we can do this better, so we introduced <code>ArgumentReducers</code>. These simple classes may reduce an argument from its complex form into a more simple readable form.</p>
<p>You can define them as such in the backtrace package:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #8FBCBB">Backtrace</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">createForThrowable</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">withArguments</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">reduceArguments</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">BaseTypeArgumentReducer</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ArrayArgumentReducer</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">StdClassArgumentReducer</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">EnumArgumentReducer</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ClosureArgumentReducer</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SensitiveParameterArrayReducer</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DateTimeArgumentReducer</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DateTimeZoneArgumentReducer</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SymphonyRequestArgumentReducer</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">StringableArgumentReducer</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">frames</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>For example, let's take a look at the <code>DateTimeArgumentReducer</code>, it takes a <code>DateTimeInterface</code> object so, for example, a <code>DateTimeImmutable</code> or <code>Carbon</code> object, and converts it into a string:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DateTimeArgumentReducer</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ArgumentReducer</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">argument</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ReducedArgumentContract</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">argument</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">instanceof</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DateTimeInterface</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">UnReducedArgument</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ReducedArgument</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">argument</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">format</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">d M Y H:i:s e</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">get_class</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">argument</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Since every argument is passed into each reducer, we'll need to specify that a reducer cannot reduce the argument. We return an <code>UnReducedArgument</code> object when the argument is not a <code>DateTimeInterface</code> object.</p>
<p>When we can reduce the argument, we return a <code>ReducedArgument</code> object with the reduced value, in this case, a string representation of the date and the original type of the object. So we can show you if a <code>DateTime</code>, <code>CarbonImmutable</code>, or any other object was passed as an argument.</p>
<p>Both these objects implement the <code>ReducedArgumentContract</code>, which makes it possible to return them both in the same function.</p>
<p>The package can now take the arguments we used to call the function/method and reduce them into readable values. The quick approach we described above will be used if no reducer can be found for a value.</p>
<h3>Putting it all together</h3>
<p>So we now have our reduced argument values and our <code>ProvidedArgument</code> objects. Now let's combine this information:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">argumentsCount</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">arguments</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">foreach</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">providedArguments</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">index</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">providedArgument</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">index</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">argumentsCount</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">providedArgument</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">defaultValueUsed</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">elseif</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">providedArgument</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">isVariadic</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">providedArgument</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">setReducedArgument</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">VariadicReducedArgument</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">array_slice</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">arguments</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">index</span><span style="color: #ECEFF4">)))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">providedArgument</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">setReducedArgument</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">arguments</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">index</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>So we loop over each <code>ProvidedArgument</code> object and try to match it with a stack trace argument. For each step in the loop, we have three cases:</p>
<p>a) we're passed the arguments provided by the stack trace, which means that the argument was not provided and a default value is used. Luckily we've reflected that value earlier.<br />
b) if the argument is variadic, we know it is the last argument. So we set the value of the <code>ProvidedArgument</code> object to a special kind of <code>ReducedArgumentContract</code> object: the <code>VariadicReducedArgument</code>, which is an array of all the values still left in the stack trace arguments list.<br />
c) In The default case, we map the value of a stack trace argument directly to its <code>ProvidedArgument</code> object.</p>
<p>That's it. There's a lot more happening to handle some more edge cases. You can check out the <a href="https://github.com/spatie/backtrace/pull/16">PR</a> if you wish.</p>
<h3>A small problem</h3>
<p>When running the tests for this feature on GitHub Actions, we noticed that no stack trace arguments were provided, even though we explicitly asked the package to include them.</p>
<p>Since PHP 7.4, there is an ini setting: <code>zend.exception_ignore_args</code>, which is default enabled on Linux machines. This setting will always hide arguments from exception stack traces which is a bummer.</p>
<p>It can be turned off within your ini or by using this piece of PHP code:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">ini_set</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">zend.exception_ignore_args</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"></span></code></pre>
<h2>Conclusion</h2>
<p>You'll find all the information required about writing ArgumentReducers and how to enable them in our docs. Also, if you're uncomfortable with your arguments being shared with Flare, this feature can be turned off.</p>
<p>We currently have plans in our pipeline to add this feature to our JS clients. Give this feature a thumbs up on our <a href="https://github.com/spatie/flareapp.io-roadmap/discussions">Flare Roadmap</a> if you want to see this in the future.</p>
<p>Further, we're constantly improving Flare now our redesign is out, so expect to see some prominent new features in the future. Step on the Flare train and enjoy the ride!</p>
]]>
            </summary>
                                    <updated>2023-07-02T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Flare 2.0 has been launched!]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/flare-20-has-been-launched" />
            <id>https://flareapp.io/flare-20-has-been-launched</id>
            <author>
                <name><![CDATA[Sebastian]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>These past months, we've been busy redesigning our entire service. We're very proud to launch it today. Long-time users will immediately notice that both our marketing website and application have a new coat of paint.</p>
<p>We didn't only update the looks. We've also added some new features and improved others in the process.This blog post will look at Flare 2.0's history, features, and used technology. Let's dive in!</p>
<p>Instead of reading this blog post, you can see it all by yourself by <a href="/register">starting a free 10-day trial</a> now! Should you want to start using Flare after 10 days, we now offer <a href="/pricing">a very affordable hobby plan</a>.   You can help us spread the good news by retweeting our <a href="https://twitter.com/freekmurze/status/1667084817362452480">launch tweet</a>.</p>
<h2>What is Flare?</h2>
<p>In most cases, you start developing an app locally on your computer. At some point, the finished application is deployed to your production server, ready to use for the world!</p>
<p>Most applications are developed locally. On your computer, debugging can be done without hassle. You can place some <code>dd</code>, <code>dump</code> <a href="https://myray.app">or </a><a href="https://myray.app"><code>ray</code></a> statements within the application, or use xDebug. When an error occurs locally, an error message will be shown locally. In Laravel applications, errors are shown via Ignition, Laravel's default error page, which we created as well. More on that later. Here's how Ignition looks like for a common error in Laravel.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/ctlocoodglznojbg2dyqcozxjk0ajpw7zx7rtsxy.jpg" alt="" /></p>
<p>When deploying your application in production, adding <code>dd</code>'s can break your whole application, and running Xdebug isn't possible. Ignition is also disabled on production because you don't want to show the inner workings of your code, possible environment variables, queries, and so much more to the users visiting your app.</p>
<p>What if you could keep Ignition on production but not show it to your users? That's where Flare comes in. Instead of showing the error in the browser of your application's user, you can let your application send it to Flare. In Flare, you can see exactly what went wrong, and this information will help you fix the issue. Here's what the error above looks like in Flare.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/phcrd4olmid1p1psxuavfarjyye1n7gx53vzw4zb.jpg" alt="" /></p>
<p>Flare will also send you a notification (via Mail, Slack, Telegram, webhooks...) when an error happens. This way, you can be aware of and fix the problem even if your user doesn't contact you.</p>
<p>In Flare, you get all of the niceties Ignition provides, like the queries which ran, possible solutions for the problem, job context if the exception happened on the queue, LiveWire data passed to your components, and so much more!</p>
<p>You can link exceptions with GitHub issues and track who in your team is working on the issue, and if it has already been solved. Our graphs provide insight into which applications have the most errors and which should be fixed first. You can even see which users had the error and contact them.</p>
<p>If you have a JavaScript frontend (or even a backend application), then Flare will be an ideal companion. We support the major frameworks like Vue and React or vanilla JS if that's your thing. You can upload sourcemaps that make your obfuscated code make sense again.</p>
<h2>Four years of Flare history</h2>
<p>Let's take a look at how Flare initially came to be. The first commit on the Flare repository was pushed on the 12th of February 2019, more than four years ago! Oh, how time flies.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/n1ogas1kdelu1cuap1rgsvencihxkmgjvvqluqdq.png" alt="" /></p>
<p>The commit message &quot;fire torpedoes&quot; was a wink at the working title of our product: &quot;Photon&quot;. Before launching our product, we decided that Photon sounded too aggressive, and after a long process, we landed on the name Flare. Notice that both the names Photon and Flare revolve around &quot;Light&quot;, which is the theme behind the early naming scheme of Laravel (Illuminate, Spark, Nova, …). In the early days of Flare, Marcel Pociot was also involved in building Flare.</p>
<p>During the development process, we tried new technologies that weren't fully released yet. We were one of the first users of <a href="https://vapor.laravel.com">Laravel Vapor</a>, the best serverless platform for Laravel apps. We also were very early adopters of Inertia.js, which we loved immediately. Our colleague Seb even coded the Inertia.js React adapter, which we still use today!</p>
<p>Our project deadline was set for Laracon EU 2019 in August. Within six months, we built not only the Flare application from scratch but Ignition as well. Ignition would go on to become the default error page in Laravel.</p>
<p>At Laracon EU 2019, Flare was <a href="https://www.youtube.com/watch?v=RP1mU4gfNeQ&amp;t=965s">shown</a>  in true Apple Keynote fashion to the public. Back at the Spatie office, all of us not attending Laracon were prepping the release and ensuring everything went live when the keynote was over. Everything went smoothly, and from that point in time, Flare was live!</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/9l0piglufg4jqazmhbhk8s1ldwjkuphboiwpff2k.jpg" alt="" /></p>
<p>In the next months and years, we didn't sit still and added a lot of new functionality to Flare. Here are some highlights:</p>
<ul>
<li>
<p><a href="https://flareapp.io/blog/23-introducing-our-github-integration">An integration with GitHub</a></p>
</li>
<li>
<p><a href="https://flareapp.io/blog/6-introducing-javascript-error-tracking">The JS error tracking which didn't exist when Flare launched</a></p>
</li>
<li>
<p><a href="https://flareapp.io/blog/13-mail-notifications-now-allow-to-snooze-and-resolve-errors">Better notifications with actions like resolving the error</a></p>
</li>
<li>
<p><a href="https://flareapp.io/blog/14-introducing-the-monthly-error-report-mail">Monthly error report emails</a></p>
</li>
<li>
<p><a href="https://flareapp.io/blog/22-meet-the-new-projects-overview-with-error-trends-and-favourites">A new projects overview with trends and favorites</a></p>
</li>
<li>
<p><a href="https://flareapp.io/blog/25-hello-there-new-api">An API</a></p>
</li>
<li>
<p><a href="https://flareapp.io/blog/9-flare-can-now-notify-you-via-discord-and-microsoft-teams">New notifications channels</a></p>
</li>
<li>
<p><a href="https://flareapp.io/blog/38-flares-new-job-tab-knows-all-about-your-failed-jobs">More info about your failed jobs</a></p>
</li>
<li>
<p><a href="https://flareapp.io/blog/42-better-support-for-livewire-in-flare-and-ignition">Context from LieWire components</a></p>
</li>
<li>
<p><a href="https://flareapp.io/blog/27-retry-requests-using-curl">Curl commands to retry requests</a></p>
</li>
<li>
<p><a href="https://flareapp.io/blog/36-building-a-better-search-with-monaco-and-amcharts">A better error search + occurrence graph for a project</a></p>
</li>
<li>
<p>And so much more ...</p>
</li>
</ul>
<p>Since Flare became so stable, we had some time to think about the next big step, and that's when we started working on the redesign, Flare v2.</p>
<h2>Why redesign Flare?</h2>
<p>When Flare was launched, we were delighted with Ignition becoming the default exception page in Laravel. We always intended to have a unified experience for handling exception messages. Laravel users should feel right at home in Flare, as they already know Ignition visually.</p>
<p>Of course, design trends change over time. Early last year, we decided to work on a refresh of how Ignition looks. It was released around one year ago. Our initial plan was to launch the redesign for Flare around the same time, but unfortunately, we couldn't pull this off. You might think that Spatie is a big firm with many people, but we're quite a small team. We also perform client work, and our full schedule didn't allow us to complete the Flare redesign.</p>
<p>Instead of shipping something half-baked, we made the difficult decision to postpone the redesign.</p>
<p>When we had time to spend on Flare again, we decided to speed things up by letting the good people at <a href="https://digitalwithyou.com">DWY</a> develop a new design for the marketing website from scratch. Dieter and Steve of DWY did a terrific job by providing a Sketch file from which we could cut the whole website.</p>
<p>With DWY having redesigned the marketing website, our app, which was still in its old style, looked way off. We decided to take hints from the marketing website to redesign our app. We didn't redesign it in one go but did several iterations. In these screenshots, you can see our app UI evolve.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/j59kunowpmfi1prh3jtsh2rhv0xxzm5aqy0qearb.png" alt="" /></p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/szezkw9nibn3b7fe0vwmizchk1vwxpuqsfv0hzfo.png" alt="" /></p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/tknzu9es4fy5vw2lbs80s8txreaoeretjqxkyodx.png" alt="" /></p>
<h2>What's new in Flare</h2>
<p>We worked intensively with the whole team on this redesign and added some new functionality next to the new design. Let's go through it!</p>
<h3>New onboarding</h3>
<p>There's now a new onboarding experience. When you log in for the first time, you're invited to create a new project.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/6xuw7gasholsv1nleg66fc7bwyaxkxva3td0ub1q.jpg" alt="" /></p>
<p>Here, you can create your very first project. Let's go ahead a pick Laravel as the used technology.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/pdznnue2oktawpxun4fw7u9f845rqm3d3pjal67i.jpg" alt="" /></p>
<p>Next, we show detailed instructions on how to configure your Laravel app. You now get instructions tailored for the Laravel version that you use.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/0oysja46tgkbb5iv2mjtiyifpodp6xtcgxashnnb.jpg" alt="" /></p>
<p>In one the final steps of the installation process, we added some real-time feedback to show you when we successfully received the first error sent by your app.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/l69z7dppel4vk39rlkwvqdmeajdsq5x6ee01flwi.jpg" alt="" /></p>
<p>For JavaScript projects, we also offer similar specific instructions for the framework and tools you use</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/0lc5rc6roehdheytv2r8rjnqckjebkbpxeujhuqr.jpg" alt="" /></p>
<h3>A new dashboard</h3>
<p>We've completely rebuilt the dashboard from scratch. You'll immediately notice that we added a big error graph and the most important metrics for each project.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/i9yoelna4iywopypgjwczbytnviwunvd0liajovk.jpg" alt="" /></p>
<p>Fear not; if you rather have a simple list of all your projects, you can pick that list view at the top.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/qgaixuwxmmmdvqzqeffpqg392cnxjox5jye7drh3.jpg" alt="" /></p>
<p>As you can see above, we've added a visual distinction between your projects' different stages and languages. A project can be set as production, staging, development, or local, each with its own color.</p>
<h3>The new error list</h3>
<p>When you open up one of our projects, you'll see all the errors for that project. The error list is completely revamped.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/ze73gpwvn0hicoyogrst64iprg2ibrtkkrntdazo.jpg" alt="" /></p>
<p>On top, you can see a graph showing the total of errors received at a time for that project. You can drag a period on that graph, and in the list, we'll only show the errors that occurred in that period.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/v4ypjztwrnofn41clw3wyvflvrltehdx4faj3hl0.jpg" alt="" /></p>
<p>You'll notice that we now also show graphs for all individual errors, so you better understand when and how often they occurred. When you hover one of the bars, you'll see the exact timeframe and error count.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/6qlmggj4v0hceujteggg8efjbnywtavszohzaiqa.jpg" alt="" /></p>
<p>On an error card, you'll notice that we add relevant, and sometimes Laravel-specific, info about that error. If your error occurred in a web request, we show the controller.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/wszkx7s2ly8o7hr43tqotxqkhl1wgj0yuwqw4rej.jpg" alt="" /></p>
<p>If it happened in an Artisan command, we show that command.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/wfmgph0rpbg15xjajwtalwn7m8qlkwowrj9tmcdi.jpg" alt="" /></p>
<p>On the error cards, you'll find buttons to mark the error as resolved and snooze notifications. You can't see this visually, but we're using optimistic mutations when you use one of the buttons. That means when you click the button, you'll immediately get visual feedback that the error is resolved/snoozed; we don't wait for a positive response code from our internal API. This will make our UI feel lightning-fast.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/tgzi03ktvs7x3jaipuamyfnsaetscxaczdeln7vj.jpg" alt="" /></p>
<h3>The revamped error screen</h3>
<p>Next, let's look at the screen you'll see when clicking on one of the errors in the project's error list. It's arguably the most important screen of Flare: here's what the error details screen looks like.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/mbf3l5mvvtyjs93eerllmt5m5qjorgsfyah61w1b.jpg" alt="" /></p>
<p>You can see that it's completely revamped. We start by showing the same error card with all details that you saw in the list.</p>
<p>When scrolling down, you'll see the stack trace of the error. It should feel very familiar, as it's rendered by the same component that also renders Ignition, the default Laravel error page.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/ykzp7tky7dgfrttbdlytkpb3ix52gtzewal1rep8.jpg" alt="" /></p>
<p>When scrolling down some more, you'll see all the app-specific details, also shown in Ignition—a true treasure trove of information.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/iwgrnnlaoqdkhu2km3chxkpbq5kbmdnuxl4oxnvf.jpg" alt="" /></p>
<h4>Error insights</h4>
<p>Scroll back to the top of an error, and you'll see one of the most significant new features: error insights.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/nlhozfaoglel7g0rqcoyoyhgb2r529ddewbovdaf.jpg" alt="" /></p>
<p>Using these insights, you can quickly see all the places where an error occurred and which of your users encountered it.</p>
<p>The URL insight shows a list of URLs where this particular URL was seen.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/bf0k0pr0fttiljqgeuoyf7su01stsmr3riuvg8xc.jpg" alt="" /></p>
<p>Each insight has a &quot;Download CSV&quot; button in the top right corner that allows you to download a list with results.</p>
<p>Let's take a look at a few other insights. Here are the jobs an error occurred in:</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/gxewbcqvoawnetxmaim67ucxkhbs0gv3xyaurdly.jpg" alt="" /></p>
<p>We think the users insight, which shows the list of users that encountered the error, will be very helpful. Here's a screenshot with fake, seeded email addresses.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/lxwkewkpc1t9ducmucpanq1uwt1rd7s5ll6h7u2b.jpg" alt="" /></p>
<h3>AI Solutions</h3>
<p>Flare already had solutions, which gave you some insight into the error and what might be the problem. We're now extending this list with AI solutions; these solutions will be generated using a GPT model.</p>
<p>Here's an error what occurred because there were too many connections to MySQL. The AI suggests some possible fixes and includes links to relevant documenation.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/mdh25acysogyh3qjzufh6gv7pyyvh2sqofdgjykq.jpg" alt="" /></p>
<p>We're very proud of this feature and can learn more about how it works in <a href="https://flareapp.io/blog/54-flare-and-ignition-now-offer-ai-powered-solutions">the separate blog post on AI solutions</a>.</p>
<h3>Project switcher</h3>
<p>An interface element that has got more common these years is a switcher. In our redesign, we added a simple project switcher you can invoke with Cmd+K on Mac or Ctrl+K on Windows, allowing you to jump quickly to another project.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/pp1eiwphshxlufv7amltjimrhwbsmtgmjjelgyig.jpg" alt="" /></p>
<p>No surprises there.</p>
<h3>Simplified team management</h3>
<p>In the previous version of Flare, you would have an overview of all the projects for all the teams you belonged to. Internally, this made grouping and sorting complicated.</p>
<p>Since only 2% of our users have more than one team, we decided to simplify this.</p>
<p>From now on, you'll see the projects for the team you're working in. Switching to another team can be done by clicking on your user avatar.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-20-has-been-launched/y6xl74w15qw3jp3uppeoraovulrptorl1bfx6fyd.jpg" alt="" /></p>
<h2>Technologies used</h2>
<p>Flare is a love letter to the Laravel community. Behind the scenes, Flare is powered by the latest version of Laravel and PHP (at the time of writing, L10 and PHP 8.2). Of course, inside our codebase, we use <a href="https://spatie.be/open-source">a gazillion of our own packages</a>.</p>
<p>Our payments are handled by <a href="https://www.google.com/search?client=safari&amp;rls=en&amp;q=laravel+cashier&amp;ie=UTF-8&amp;oe=UTF-8">Laravel Cashier</a> and <a href="https://stripe.com">Stripe</a>.</p>
<p>Our error ingress point is managed by <a href="https://www.cloudflare.com">Cloudflare</a>. We use that service for authentication and spike protection.</p>
<p>The front end of our app is built us React, TypeScript, and InertiaJS.</p>
<p>Our admin panel, which is used to quickly look up customer info and impersonate users, is powered by Filament.</p>
<p>Though we started on <a href="https://vapor.laravel.com">Laravel Vapor</a>, it was more cost-effective for us to use regular servers. These servers are hosted on Digital Ocean and provided by <a href="https://forge.laravel.com">Laravel Forge</a>.</p>
<h2>In closing</h2>
<p>We have put a lot of love into the Flare redesign, and we hope it shows. Of course, this is not the end of development on Flare. With the redesign launched, we can now concentrate on adding more features to Flare that are on our roadmap.</p>
<p>One of the bigger things we'll concentrate on is something not visible to our users. We've experimented with <a href="https://www.singlestore.com">SingleStore</a> and are pretty impressed by the performance improvements over MySQL it offers. We're going to experiment if we can improve the response times for Flare by making the switch to SingleStore. We'll be sure to write a few blog posts on that process as well.</p>
<p>Now is the perfect time to try out Flare. We support both PHP and JS projects. For Laravel apps, we've gone the extra mile: we recognize Livewire errors, for an error in a queue job, we display information about that queue, view errors will contain the decompiled Blade view, ... You can <a href="https://flareapp.io">register here</a> for a free ten-day trial; no credit card is required.</p>
]]>
            </summary>
                                    <updated>2023-06-08T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Flare and Ignition now offer AI-powered solutions]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/flare-and-ignition-now-offer-ai-powered-solutions" />
            <id>https://flareapp.io/flare-and-ignition-now-offer-ai-powered-solutions</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>One of the unique features of Flare is that it can display solutions for your errors. In our code base, we try to detect specific error messages and display a solution when we recognize a specific error.</p>
<p>Today, we're adding AI-powered solutions to Flare. This means we can suggest a solution for almost any error: the AI will likely suggest a helpful suggestion for most errors.</p>
<p>Here's an example where we forgot to configure the region for an S3 disk. &quot;Region must be a valid RFC host label.&quot; doesn't say much, but the AI clearly explains that we should pass a region like <code>us-west-2</code> and even links to the list with all possible regions.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-and-ignition-now-offer-ai-powered-solutions/iue9dlk3y6fvbpioo8u5i2kij3rwlcvvhme3ogjr.jpg" alt="" /></p>
<p>We don't see AI-powered solutions as a &quot;revolutionary&quot; feature: they are just icing on our cake. The best feature of Flare is our UI and hand-crafted optimizations for both Laravel and PHP. Together with AI solutions, this will help you better understand an error and fix them faster.</p>
<p>In this blog post, I'd like to share more about how we implemented AI solutions in both Flare and Ignition.</p>
<h2>Introducing Flare</h2>
<p>Before heading into AI-land, I'll explain what Flare is to get everybody on the same page. If you're familiar with Flare, skip to the next section.</p>
<p>If something goes wrong in your production environment, and you don't use an error tracker, it is straightforward to miss that an error happened. If you're lucky, one of your users will contact you about it (which is a bit embarrassing), or they won't contact you about it (which is even worse because you won't have a chance to fix things).</p>
<p>When you use an exception tracker like Flare, your app will send the error to Flare, and Flare will send you a notification via Mail, Slack, or ... The notification will contain a link to a full report on the error, giving you as many details as possible. You can use this information to fix your app, even if your user doesn't contact you.</p>
<p>When you use an exception tracker like Flare, your app will send the error to Flare, and Flare will send you a notification via Mail, Slack, or ... The notification will contain a link to a full report on the error, giving you as many details as possible. You can use this information to fix your app, even if your user doesn't contact you about it.</p>
<p>At Spatie, we build and maintain huge apps ourselves. We noticed that existing error trackers have confusing UIs that bombard you with too much information. We built Flare with ease of use in mind. We aim to show you the most relevant information so that you can fix errors faster in all your PHP and JS applications.</p>
<p>If you use Laravel, you already know what Flare looks like. When you have an error happening in your local Laravel app, you'll see an error page. We made that error page - it's called Ignition.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-and-ignition-now-offer-ai-powered-solutions/8wtxtnr5veyqa4qontgkt4cgxme0sf7lg7sqscqp.jpg" alt="" /></p>
<p>In Flare, errors are displayed similarly as they appear on Ignition; you already know where the important bits of info are.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-and-ignition-now-offer-ai-powered-solutions/lxkbms8m9twhrki4pihkhxs18my9o0r3f1bfpuqp.jpg" alt="" /></p>
<p>We've gone the extra mile to report errors from a Laravel app: we recognize Livewire errors; for an error in a queue job, we display information about that queue; view errors will contain the decompiled Blade view, ...</p>
<h2>AI-powered solutions in Flare</h2>
<p>Getting started with an AI-powered solution in Flare is very easy. Generating an AI solution requires sending your errors to a third party (in this case, OpenAI). We only want to do that with your explicit approval; you need to enable the feature in the AI settings of Flare manually.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-and-ignition-now-offer-ai-powered-solutions/jzci2t9ybyo2mkns0tiuoozfwylsfqcvb87fayjr.jpg" alt="" /></p>
<p>When this feature is enabled, we will send the 500 unique incoming errors to OpenAI, where a solution will be generated. Notice that it'll always include a few helpful links to documentation.<br />
<img src="https://content.spatie.be/assets/flare/flare-and-ignition-now-offer-ai-powered-solutions/iue9dlk3y6fvbpioo8u5i2kij3rwlcvvhme3ogjr.jpg" alt="" /></p>
<p>Of course, we also display the solution in all notifications we send. Here's an example of a mail notification containing an AI solution.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-and-ignition-now-offer-ai-powered-solutions/4ohhydprnmmlkfxbgy7tohhbca0nrrvyhbjs19f2.jpg" alt="" /></p>
<p>And here's how a Slack notification looks like:</p>
<p><img src="https://content.spatie.be/assets/flare/flare-and-ignition-now-offer-ai-powered-solutions/h9ugjp0qm0rzuezzxigruveydba932z8jqc9hh7q.jpg" alt="" /></p>
<p>There's a cost involved in generating AI solutions, and that's why we have a daily limit of 500 errors that will send to OpenAI. Don't worry: if you cross this limit, we'll still accept and display incoming errors, but we'll no longer generate a solution via OpenAI.</p>
<p>If you want to generate more than 500 solutions per day via OpenAI, you can use your own OpenAI key. When using your own key, you can specify your own daily limit.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-and-ignition-now-offer-ai-powered-solutions/gq0jpfkfrqbfqirjyv56lxtovmail2cahml5vq2m.jpg" alt="" /></p>
<h2>How this works behind the scenes</h2>
<p>Interacting with (Open)AI sounds advanced, but it's pretty simple. In short, we convert the error to a textual prompt and make an API call to OpenAI. The service responds with a solution. The workflow is more or less the same as in <a href="https://beyondco.de/blog/ai-powered-error-solutions-for-laravel">this blog post by Marcel</a>.</p>
<p>When AI solutions are enabled, we'll send these parts on an incoming error to Open AI:</p>
<ul>
<li>
<p>the error message</p>
</li>
<li>
<p>the error class</p>
</li>
<li>
<p>the stack trace</p>
</li>
<li>
<p>the framework version</p>
</li>
<li>
<p>little bits of context to help the AI generate a proper response</p>
</li>
</ul>
<p>We don't send anything revolving around environment variables or request payload to avoid sending passwords to the AI.</p>
<p>To convert an error to a question (aka the prompt) that we'll send to the AI, we use this Blade view. I've simplified it a bit to keep it concise.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">You are a talented web programmer</span><span style="color: #81A1C1">.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">@if</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">language</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">You are working </span><span style="color: #81A1C1">on</span><span style="color: #D8DEE9FF"> a </span><span style="color: #ECEFF4">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">language</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}}</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">languageVersion</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}}</span><span style="color: #D8DEE9FF"> app</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">@if</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">framework</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	    that uses </span><span style="color: #ECEFF4">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">framework</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}}</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">frameworkVersion</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">@endif</span></span>
<span class="line"><span style="color: #81A1C1">@endif</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">Use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">the</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">following</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">context</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">to</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">find</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">a</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">possible</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">fix</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">for</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">the</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">exception</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">message</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">at</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">the</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">end</span><span style="color: #D8DEE9FF">. </span><span style="color: #8FBCBB">Limit</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">your</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">answer</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">to</span><span style="color: #D8DEE9FF"> 4 </span><span style="color: #8FBCBB">or</span><span style="color: #D8DEE9FF"> 5 </span><span style="color: #8FBCBB">sentences</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">and</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">include</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">links</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">to</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">documentation</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">that</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">might</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">help</span><span style="color: #D8DEE9FF">. </span><span style="color: #8FBCBB">Also</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">rate</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">on</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">a</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">scale</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">from</span><span style="color: #D8DEE9FF"> 1 </span><span style="color: #8FBCBB">to</span><span style="color: #D8DEE9FF"> 100</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">how</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">sure</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">you</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">are</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">that</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">this</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">is</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">the</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">correct</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">fix</span><span style="color: #D8DEE9FF">. </span><span style="color: #8FBCBB">Avoid</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">repeating</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">the</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">exception</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">message</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">in</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">your</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">answer</span><span style="color: #D8DEE9FF">.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">this</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">format</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">in</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">your</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">answer</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> make sure the links are JSON</span><span style="color: #81A1C1">.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">FIX</span></span>
<span class="line"><span style="color: #D8DEE9FF">insert the possible fix here</span></span>
<span class="line"><span style="color: #D8DEE9FF">ENDFIX</span></span>
<span class="line"><span style="color: #D8DEE9FF">LINKS</span></span>
<span class="line"><span style="color: #ECEFF4">{</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">title</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Title link 1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">URL</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">URL link 1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">{</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">title</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Title link 2</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">URL</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">URL link 2</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">ENDLINKS</span></span>
<span class="line"><span style="color: #D8DEE9FF">SCORE</span></span>
<span class="line"><span style="color: #D8DEE9FF">score from </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> to </span><span style="color: #B48EAD">100</span></span>
<span class="line"><span style="color: #D8DEE9FF">ENDSCORE</span></span>
<span class="line"><span style="color: #81A1C1">---</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">Here comes the context </span><span style="color: #81A1C1">and</span><span style="color: #D8DEE9FF"> the </span><span style="color: #81A1C1">exception</span><span style="color: #D8DEE9FF"> message</span><span style="color: #81A1C1">:</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">Line: </span><span style="color: #ECEFF4">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">lineNumber</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">File:</span></span>
<span class="line"><span style="color: #ECEFF4">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">file</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">Snippet including line numbers</span><span style="color: #81A1C1">:</span></span>
<span class="line"></span></code></pre>
<p>{{ $error-&gt;snippet }}</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">Exception </span><span style="color: #81A1C1">class:</span></span>
<span class="line"><span style="color: #ECEFF4">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">exceptionClass</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">Exception message</span><span style="color: #81A1C1">:</span></span>
<span class="line"><span style="color: #ECEFF4">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">exceptionMessage</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}}</span></span>
<span class="line"></span></code></pre>
<p>To send this prompt to Open AI, we use <a href="https://github.com/openai-php/client">this excellent client</a>.</p>
<p>You can see that in the prompt above, we ask the AI nicely to use the template to format the message, format the links as JSON, and score its own response. Believe it or not, the AI follows these instructions perfectly.</p>
<p>This allows us to parse the response and make decisions based on the score. If the AI score its own answer below a certain threshold, we'll not use the solution. Sometimes the AI  responds with links to documentation that result in a 404. That's why we, behind the scenes, have some code to verify that all the links the AI returns are valid.</p>
<p>Because Flare already featured displaying solutions long before the dawn of AI, we already had the necessary DB tables and UI in place. The only thing we added was a flag to remember that a solution was generated by UI. Using this flag, we can visually indicate that the solution was AI-generated, and we also add a little disclaimer message.</p>
<h2>Using AI locally in Ignition</h2>
<p>Using Flare is probably the easiest way to work with AI solutions, as you only have to enable the feature, and you're done.</p>
<p>But we didn't stop with just adding AI solutions to Flare; we added them to Ignition (both the Laravel and the framework agnostic variant) as well.</p>
<h3>In Laravel</h3>
<p>Support for AI solutions was added in Laravel-ignition v2.1. Run <code>composer update</code> to pull that in.</p>
<p>Next, you must first install this optional dependency. We need this package to communicate with open AI.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">composer </span><span style="color: #81A1C1">require</span><span style="color: #D8DEE9FF"> openai</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">php</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">client</span></span>
<span class="line"></span></code></pre>
<p>Finally, you must specify an OpenAI API in the <code>IGNITION_OPEN_AI_KEY</code> key in your <code>.env</code> file. You can generate this key <a href="https://platform.openai.com">at OpenAI</a>.</p>
<p>We use cache to minimize calls made to Open AI when your application generates similar errors.</p>
<p>With this in place, each error your local app makes will be transmitted to OpenAI, and an AI-generated solution will be displayed next to the error message.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-and-ignition-now-offer-ai-powered-solutions/trfxaxbf7rinrwmhelunrsni50habfcb8joitrur.jpg" alt="" /></p>
<h3>In any PHP app</h3>
<p>Laravel comes with the Laravel-specific variant of Ignition installed. That version is based on <a href="https://github.com/spatie/ignition">a framework-agnostic version of Ignition</a> that can be used in any PHP application.</p>
<p>After installing <code>spatie/ignition</code> in your PHP app, you must instantiate the <code>OpenAiSolutionProvider</code>. The constructor expects an OpenAI API key to be passed; you should generate this key <a href="https://platform.openai.com">at OpenAI</a>.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Ignition</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Solutions</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">OpenAi</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">OpenAiSolutionProvider</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">aiSolutionProvider</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OpenAiSolutionProvider</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">openAiKey</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>To use the solution provider, you should pass it to <code>addSolutionProviders</code> when registering Ignition.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Ignition</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Ignition</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addSolutionProviders</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">aiSolutionProvider</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// other solution providers...</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Let's try it out; here's a simple PHP script with Ignition installed.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cache</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ArrayStore</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cache</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Repository</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Ignition</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Ignition</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Ignition</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Solutions</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">OpenAi</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">OpenAiSolutionProvider</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">include</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">vendor/autoload.php</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">openAiKey</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">sk-your-open-ai-key-here</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Ignition</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addSolutionProviders</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">OpenAiSolutionProvider</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">openAiKey</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Repository</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ArrayStore</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Exception</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Stop here</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>This is what we see in the browser when we try to render a webpage using that script.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-and-ignition-now-offer-ai-powered-solutions/gcxawwkaokc5zejcysvr0a4ni8dz5caut768s0r1.jpg" alt="" /></p>
<p>Optionally, you can add caching so similar errors will only get sent to OpenAI once. To cache errors, you can call <code>use cache</code> on <code>$aiSolutionProvider.</code> You should pass <a href="https://packagist.org/providers/psr/simple-cache-implementation">a simple-cache-implementation</a>. Here's the signature of the <code>use cache</code> method.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">useCache</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">CacheInterface</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cache</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheTtlInSeconds</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">60</span><span style="color: #D8DEE9FF"> * </span><span style="color: #B48EAD">60</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<p>To increase the quality of the suggested solutions, you can send along the application type (Symfony, Drupal, WordPress, ...) to the AI.</p>
<p>To send the application type, call <code>application type</code> on the solution provider.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">aiSolutionProvider</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">applicationType</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Symfony 6</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<h2>In closing</h2>
<p>We hope that you like the addition of AI solutions to Flare and Ignition. As said in the intro, we think that AI solutions will not revolutionize our service (it's already great without AI solutions), but that AI can help you better in many situations.</p>
<p>For now, we've only enabled AI solutions for PHP and Laravel applications. In the next few weeks, we'll polish our prompt based on our users' feedback. After that, we'll experiment if we can give good solutions for JavaScript errors.</p>
<p>Flare is the best error tracker for Laravel and PHP projects. We display the most important aspects of issues in your production environment in our beautiful UI.</p>
<p>Now is the perfect time to try out Flare. You can <a href="https://flareapp.io">register here</a> for a free ten-day trial; no credit card is required.</p>
]]>
            </summary>
                                    <updated>2023-06-08T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Sensitive parameters in PHP 8.2]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/sensitive-parameters-in-php-82" />
            <id>https://flareapp.io/sensitive-parameters-in-php-82</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When debugging errors, we as developers have a few tools available: an exception of a particular class, a message, a status code, and a stack trace. The stack trace is a report that provides information about the sequence of function calls that led to an error or an exception. It shows the order in which functions were called.</p>
<p>We name each call to a function/method within a stack trace a frame. It has lots of useful info like the file name, line number, function name, class (sometimes), and arguments that were used to make the call. Having arguments within a stack trace can be dangerous. For example, let's say you have a function like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getPayments</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">apiKey</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Exception</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Could not fetch payments</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Usually, you would have some application code here, fetching payments through an external API, but to simplify things, let's go straight to the case where we throw an exception because the payments couldn't be fetched.</p>
<p>You can call this function as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">getPayments</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">dj8gd4sl05db32xjch7989dsxws</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>This function call, of course, fails miserably, providing you with the following exception message:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">Fatal error</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Uncaught Exception</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Could not fetch payments in </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">in</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">UoFa1</span><span style="color: #81A1C1">:</span><span style="color: #B48EAD">6</span></span>
<span class="line"><span style="color: #D8DEE9FF">Stack trace</span><span style="color: #81A1C1">:</span></span>
<span class="line"><span style="color: #616E88">#0 /in/UoFa1(32): getPayments(&#39;dj8gd4sl05db32x...&#39;)</span></span>
<span class="line"><span style="color: #616E88">#1 {main}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  thrown in </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">in</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">UoFa1 </span><span style="color: #81A1C1">on</span><span style="color: #D8DEE9FF"> line </span><span style="color: #B48EAD">6</span></span>
<span class="line"><span style="color: #81A1C1">&lt;br/&gt;&lt;</span><span style="color: #D8DEE9FF">i</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF">Process exited with code </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">b title</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Generic Error</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span><span style="color: #B48EAD">255</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">b</span><span style="color: #81A1C1">&gt;</span><span style="color: #81A1C1">.</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">i</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"></span></code></pre>
<p>Take a look at the third line:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">#0 /index.php(34): getPayments(&#39;dj8gd4sl05db32x...&#39;)</span></span>
<span class="line"></span></code></pre>
<p>That's our API key in plain sight, which we want to avoid at all costs!</p>
<p>Above, we saw the stringified version of the stack trace. When we catch the exception, we can take a look at a more normalized array version:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">getPayments</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">dj8gd4sl05db32xjch7989dsxws</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Exception</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">e</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">var_dump</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">e</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getTrace</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Catching the exception gives us the following stack trace:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">array</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">array</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">4</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">file</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">9</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/index.php</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">line</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">int</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">34</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">function</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">11</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">getPayments</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">args</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">array</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">27</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">dj8gd4sl05db32xjch7989dsxws</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>As you can see, we're passing one argument to the <code>getPayments</code> function, which can be helpful when debugging. But this information can also end up in the wrong hands when displayed in logs or error pages like Ignition.</p>
<p>That's the reason why we refrained from showing these parameters in Ignition:</p>
<p><img src="https://content.spatie.be/assets/flare/sensitive-parameters-in-php-82/eudbehy9j2nh593nb6qdv4t888wbsekjwu24ljiu.png" alt="" /></p>
<p>And thus, due to the tight coupling of Flare and Ignition, these parameters won't end up in Flare.</p>
<p>But this all might change due to a recent addition to PHP 8.2.</p>
<h2>Sensitive Parameters</h2>
<p>Starting from PHP 8.2, some parameters can now be tagged as 'sensitive' using an attribute, meaning a parameter will be hidden from a stack trace.</p>
<p>Let's rewrite our <code>getPayments</code> function as follows:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getPayments</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    #[SensitiveParameter]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">apiKey</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Exception</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Could not fetch payments</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Now when we execute the function, we get the following:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">Fatal error</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Uncaught Exception</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Could not fetch payments in </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">in</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Ko3d8</span><span style="color: #81A1C1">:</span><span style="color: #B48EAD">13</span></span>
<span class="line"><span style="color: #D8DEE9FF">Stack trace</span><span style="color: #81A1C1">:</span></span>
<span class="line"><span style="color: #616E88">#0 /in/Ko3d8(32): getPayments(Object(SensitiveParameterValue))</span></span>
<span class="line"><span style="color: #616E88">#1 {main}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  thrown in </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">in</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Ko3d8 </span><span style="color: #81A1C1">on</span><span style="color: #D8DEE9FF"> line </span><span style="color: #B48EAD">13</span></span>
<span class="line"><span style="color: #81A1C1">&lt;br/&gt;&lt;</span><span style="color: #D8DEE9FF">i</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF">Process exited with code </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">b title</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Generic Error</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span><span style="color: #B48EAD">255</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">b</span><span style="color: #81A1C1">&gt;</span><span style="color: #81A1C1">.</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">i</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"></span></code></pre>
<p>The API key is replaced with <code>Object(SensitiveParameterValue)</code>, hiding the original value! Let's see how the stack trace looks:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">array</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">array</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">4</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">file</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">9</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/in/cv0er</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">line</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">int</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">33</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">function</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">11</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">getPayments</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">args</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">array</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">object</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">SensitiveParameterValue</span><span style="color: #ECEFF4">)</span><span style="color: #616E88">#2 (0) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span></code></pre>
<p>Again the API key is gone from traces, but the value still exists. The original string value is replaced with an object <code>SensitiveParameterValue</code>, which wraps the original value. We can see this by retrieving the original value as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">getPayments</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">dj8gd4sl05db32xjch7989dsxws</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Exception</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">e</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">var_dump</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">e</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getTrace</span><span style="color: #ECEFF4">()[</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">][</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">args</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">][</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getValue</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// dj8gd4sl05db32xj...</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"></span></code></pre>
<h2>Classes</h2>
<p>The example mentioned above, used functions to make things easy to understand, but you can perfectly tag parameters as sensitive in a class:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">PaymentApi</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getPayments</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    	 </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">tenantId</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        #[SensitiveParameter]</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">apiKey</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Exception</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Could not fetch payments</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Running this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">api</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">PaymentApi</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">api</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getPayments</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">314</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">dj8gd4sl05db32xjch7989dsxws</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Exception</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">e</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">var_dump</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">e</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getTrace</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>It gives us the following stack trace:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">array</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">array</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">6</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">file</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">90</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">/index.php</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">line</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">int</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">36</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">function</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">11</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">getPayments</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">class</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">PaymentApi</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">-&gt;</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">args</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">array</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">int</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">314</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">=&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">object</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">SensitiveParameterValue</span><span style="color: #ECEFF4">)</span><span style="color: #616E88">#3 (0) {</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span></code></pre>
<p>As you can see, the <code>tenantId</code> is a non-sensitive parameter, so its value still appears in the stack trace. However, <code>apiKey</code>, the sensitive parameter value, has been wrapped into a <code>SensitiveParameterValue</code> object.</p>
<h2>Flare</h2>
<p>As mentioned, we don't support stack trace arguments in Ignition for security reasons, and this new sensitive parameter feature might let us rethink this. On our roadmap, I've created a new <a href="https://github.com/spatie/flareapp.io-roadmap/discussions/71">feature request</a>, allowing arguments to be shown in Ignition and Flare stack traces. Give it a thumbs up if you want to see this implemented.</p>
]]>
            </summary>
                                    <updated>2023-06-05T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Caching Inertia's SSR responses]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/caching-inertias-ssr-responses" />
            <id>https://flareapp.io/caching-inertias-ssr-responses</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p><a href="https://inertiajs.com/">Inertia</a> has been widespread among Laravel developers for a few years due to its seamless integration of front-end frameworks like Vue.js and React. However, the recent addition of SSR support in Inertia opens up new possibilities for improving performance and SEO. To further enhance the performance of SSR-rendered responses, we can leverage our very own <a href="https://github.com/spatie/laravel-responsecache">spatie/laravel-responsecache</a> package, a powerful HTTP caching solution for Laravel applications. In this post, we will explore how to integrate and configure the package to cache SSR responses generated by Inertia. We'll also look at some interesting edge cases we discovered when integrating this in Flare 2.0.</p>
<h2>SSR in Inertia: A Quick Introduction</h2>
<p>Server-side rendering (SSR) is a technique that allows the server to generate and deliver fully rendered HTML pages to the browser instead of relying on the browser to execute megabytes of JavaScript to render the content.</p>
<p>In the context of Inertia, this is achieved by passing the Inertia props from Laravel to a Node.js SSR process. This way, we can utilize the server's full performance to quickly pre-render the component's HTML. On the client side, React knows how to handle this SSR response and &quot;re-hydrate&quot; it to add the required interactivity for the React components. While this may sound complicated, Inertia makes integrating SSR easier than ever, even in existing applications (like Flare!).</p>
<p>This entire SSR flow results in faster load times, improved performance, and the ability to use React components in an SEO-friendly way (as the fully rendered HTML content is always readily available for indexing by search engines).</p>
<h2>(Re)-introducing spatie/laravel-responsecache</h2>
<p><code>spatie/laravel-responsecache</code> is a versatile HTTP caching package. While it may be nothing new in 2023, combining it with SSR could be this package's most exciting use case since its release. Inertia with SSR is already pretty fast, but in-memory caching will always be faster.</p>
<p>Apart from easy response caching, the package provides flexible caching strategies, cache invalidation mechanisms, and integration with various parts of your Laravel application. We can now efficiently cache Inertia's SSR responses using some of the above features initially designed for regular HTTP requests, resulting in lower server load and faster subsequent page loads for our users.</p>
<p>Let's take a quick look at how we configured <code>spatie/laravel-responsecache</code> to work with Inertia's SSR features:</p>
<h2>Installation and Configuration</h2>
<p>Installing the response cache package is as easy as installing any other package in Laravel:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88"># use composer to install the package</span></span>
<span class="line"><span style="color: #D8DEE9FF">composer </span><span style="color: #81A1C1">require</span><span style="color: #D8DEE9FF"> spatie</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">responsecache</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88"># publish the config file</span></span>
<span class="line"><span style="color: #D8DEE9FF">php artisan vendor</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">publish </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">provider</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Spatie\ResponseCache\ResponseCacheServiceProvider</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"></span></code></pre>
<p>Finally, add the <code>CacheResponse::class</code> middleware to the (Inertia) routes you want cached:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">ResponseCache</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Middlewares</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CacheResponse</span><span style="color: #D8DEE9FF">::</span><span style="color: #8FBCBB">class</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Route</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">my-awesome-homepage</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">HomeController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">middleware</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">CacheResponse</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>And just like that, you've got basic HTTP response caching in Laravel. However, looking at the <code>laravel-responsecache</code> header in your browser's developer tools, you might notice that Inertia responses are not being cached. That's because Inertia requests are made using Axios (AJAX), and the response cache package avoids caching any AJAX requests.</p>
<h2>A custom cache profile for Inertia requests</h2>
<p>By default, <code>spatie/laravel-responsecache</code> comes configured with the <code>CacheAllSuccessfulGetRequests</code> cache profile. This profile caches (you guessed it!) all successful GET requests. However, less evident from reading the class name, it avoids caching AJAX requests. Because Inertia uses AJAX to request new pages, we need to customize the cache profile to ensure caching of these requests.</p>
<p>We'll do this by creating a new <code>InertiaResponseCacheProfile</code> class and configuring that in the <code>cache_profile</code> option of the  <code>config/responsecache.php</code> config:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">cache_profile</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">ResponseCache</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ResponseCacheProfile</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span></span>
<span class="line"></span></code></pre>
<p>The custom cache profile will work almost identically to the original <code>CacheAllSuccessfulGetRequests</code> profile. Here's what it looks like:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Request</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">ResponseCache</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">CacheProfiles</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CacheAllSuccessfulGetRequests</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">InertiaResponseCacheProfile</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">CacheAllSuccessfulGetRequests</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">shouldCacheRequest</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Request</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">ajax</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isMethod</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">get</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Cache Inertia (= AJAX) GET requests!</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">isRunningInConsole</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isMethod</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">get</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Testing this new cache profile with Inertia routes will quickly surface its biggest flaw: Inertia will return a normal HTML response on the first request to a particular endpoint and JSON responses on subsequent requests <em>to the same endpoint</em>. The important thing to note is that the response cache will not consider the response's content type. This means that whatever HTML or JSON response is returned will be cached, resulting in situations where HTML is returned when JSON is expected or vice versa.</p>
<h2>Making the response cache aware of content-type</h2>
<p>To deal with Inertia's HTML and JSON responses on the same endpoint, we must make the response cache aware of the content type. We can do this by using a custom hasher class. You can think of the hasher class as a cache key generator: it should generate a unique hash for unique requests (e.g., different content types) but the same hash for similar requests (e.g., <code>GET /?a=1&amp;b=2</code> and <code>GET /?b=2&amp;a=1</code>).</p>
<p>Let's create a custom <code>InertiaResponseCacheHasher</code> hasher class for generating a unique hash that is not only based on the request URL and method but also the request's <code>content-type</code> header:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Request</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Hashing</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">DefaultHasher</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ResolveCurrencyForCustomerAction</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">InertiaResponseCacheHasher</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">DefaultHasher</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getHashFor</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Request</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">baseHash</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">parent::</span><span style="color: #88C0D0">getHashFor</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">contentType</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">request</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getContentTypeFormat</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">baseHash</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">-</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">contentType</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>In this custom response cache hasher, we extend the <code>DefaultHasher</code> provided by the package and override the <code>getHashFor</code> method. We append the <code>content-type</code> to the base hash to maintain a different response cache for each content type.</p>
<p>If you provide different responses based on request parameters, this would be the ideal location to configure it. For instance, Flare's pricing page displays prices in either euros or dollars, depending on the IP location. By incorporating the resolved currency in the response cache key, we can prevent mistakenly serving the incorrect currency to the incorrect location.</p>
<h2>Conclusion</h2>
<p>Combining <code>spatie/laravel-responsecache</code> with Inertia can significantly enhance performance and reduce initial load times. Additionally, it enables us to fully utilize Inertia's SSR support with minimal server load and no SEO penalty. In Flare, we can combine the above to serve our marketing site and documentation as quickly as possible and ensure that it gets indexed by search engines.</p>
]]>
            </summary>
                                    <updated>2023-05-22T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Flare 2.0 is right around the corner]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/flare-20-is-right-around-the-corner" />
            <id>https://flareapp.io/flare-20-is-right-around-the-corner</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Flare was launched almost four years ago. In human life, that's a short time, but for an online service, that's a lifetime.</p>
<p>Over the years, we had quite some ideas on how to improve the user experience for Flare. You'll be happy to know that for the past few months, our team has been working hard on Flare 2.0.</p>
<p>Flare 2.0 features:</p>
<ul>
<li>
<p>a major redesign both of the marketing page and the app</p>
</li>
<li>
<p>opt-in AI solutions, which auto-generate solutions based on all the information we have on your error</p>
</li>
<li>
<p>error insights, which give you a clear overview of which jobs, commands, ... errors happened for which users</p>
</li>
<li>
<p>a new onboarding experience that makes it much easier to get started using Flare</p>
</li>
<li>
<p>an avalanche of little improvements and refinements</p>
</li>
</ul>
<p>By rethinking and streamlining the whole process, we hope to have created lots of improvements you'd like. The launch of the Flare 2.0 is planned for early June.</p>
<h2>Get access to Flare 2.0 beta</h2>
<p>We're currently running a closed beta of Flare 2.0. If you want to help us test the new design and features, <a href="mailto:support@flareapp.io">send us a mail</a>.</p>
<p>Your feedback will help us polish everything so all our users have a great first-day experience.</p>
]]>
            </summary>
                                    <updated>2023-05-15T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Changing your larger-than-average MySQL table]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/changing-your-larger-than-average-mysql-table" />
            <id>https://flareapp.io/changing-your-larger-than-average-mysql-table</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>We're still working on our redesign and are close to release. Today we started tackling an issue where the performance of the error page was too slow for us. In the end, we needed to change the structure of the error occurrences table, which is a lot harder than it seems.</p>
<p>In the redesign of Flare, we've added a new feature called insights which gives you a quick overview of some statistics like which endpoints triggered the error, which users, which application versions, and so on.</p>
<p><img src="https://content.spatie.be/assets/flare/changing-your-larger-than-average-mysql-table/myv0qk4ditmzbkdkuydesdejb0fttcieodbr0zay.png" alt="" /></p>
<p>The counts for these insights are always live calculated, which means queries like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">SELECT</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">DISTINCT entry_point</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">AS</span><span style="color: #D8DEE9FF"> aggregate</span></span>
<span class="line"><span style="color: #D8DEE9FF">FROM</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #D8DEE9FF">WHERE</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">AND</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> IS NOT </span><span style="color: #81A1C1">NULL</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">AND</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">entry_point_type</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">web</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Each error occurrence has an <code>entry_point_type</code> (web, queue, command) and an <code>entry_point</code> (a URL, job class, or command). These queries were quite performant on our seeded data, but in production, we have an error that has accumulated 66k occurrences. That's the point where things start to become slow.</p>
<p>Luckily, there's an easy solution to this, indexes! So we've added an index to <code>entry_point_type</code> and saw a performance boost. Later on, we tried adding an index to <code>entry_point</code>, but this exception popped up:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">BLOB</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">TEXT column </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">entry_point</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> used in key specification without a key length</span></span>
<span class="line"></span></code></pre>
<p>Wait! Are we using a text type column for <code>entry_point</code>? That's a bit crazy. These columns are stored differently than varchars in MySQL, which makes them a lot slower + indexing them has some limitations. Let's fix this using a varchar column, but how many characters should it be?</p>
<p>The first thing we checked was how many characters are required on average. A quick query that counted the number of occurrences for each <code>entry_point</code> length gave us the following:</p>
<p><img src="https://content.spatie.be/assets/flare/changing-your-larger-than-average-mysql-table/skgnhkxfojeb1wtczqikskbwqtdhcxskd7kgnfwr.png" alt="" /></p>
<p>We're pretty safe with 255 characters because almost all entry points for occurrences are around that length. Because a URL is somewhat limited to 2048 characters and varchars smartly assign space (it only takes the space required for a value), we took 2048 characters for this column.</p>
<p>Now changing our column can be done as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">ALTER TABLE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #D8DEE9FF">CHANGE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">entry_point</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">entry_point</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">varchar</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2048</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci </span><span style="color: #81A1C1">NULL;</span></span>
<span class="line"></span></code></pre>
<p>The thing is, this is a large table at the moment. There are 10531985 it.. 10532202 ite.. 10532278 items. You get the point. A lot of items, and the table is constantly growing.</p>
<p>By default, MySQL will make a copy of this table, apply the changes to that table, remove the old table, and use the newly created one instead. This process is extremely slow, and it blocks a lot of statements, completely shutting down Flare. We should avoid this at all costs.</p>
<h2>The trick with the extra column</h2>
<p>There are a few solutions to this problem. In our case, we did the following: we first created a new column called <code>entry_point_2</code> with the correct type:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">ALTER TABLE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">ADD COLUMN </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">entry_point_2</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">varchar</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2048</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">NULL</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> ALGORITHM</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">INSTANT</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>The MySQL instant algorithm will instantly add the column (it still takes some time, but no locks are required). The problem? When we ran this query, MySQL started copying our table, which was the thing we were trying to avoid.</p>
<p>After some research work in the MySQL documentation, we discovered that these operations will still use the copy algorithm if a fulltext index exists on the table, even when that index isn't used in the operation.</p>
<p>So the easy fix was to drop the fulltext index temporarily:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">ALTER TABLE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">DROP INDEX </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_message_fulltext</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Great, we can now add an extra column without too much hassle.</p>
<h2>Moving data</h2>
<p>Next, we move the data from <code>entry_point</code> to <code>entry_point_2</code>. This operation is going to take some time. The cool thing is we can already ensure that newly created occurrences immediately fill <code>entry_point_2</code>.</p>
<p>Doing such a thing would require some changes to the code, then deploying that code, later changing the code, deploying that code again ... too much complexity if you ask me.</p>
<p>MySQL has a feature called triggers, which are small hooks running before or after inserting, updating, or deleting a row. Since we only add error occurrences, an insert trigger was enough to make sure each new occurrence has its <code>entry_point_2</code> set:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">CREATE TRIGGER </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #D8DEE9FF">	BEFORE INSERT </span><span style="color: #81A1C1">ON</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #D8DEE9FF">	FOR EACH ROW</span></span>
<span class="line"><span style="color: #D8DEE9FF">	BEGIN</span></span>
<span class="line"><span style="color: #D8DEE9FF">	SET NEW</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">entry_point_2</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> NEW</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">entry_point</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">END</span></span>
<span class="line"></span></code></pre>
<p>Now we can update all the other entries within the table which are missing a value for <code>entry_point_2</code>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">UPDATE</span></span>
<span class="line"><span style="color: #D8DEE9FF">	error_occurrences</span></span>
<span class="line"><span style="color: #D8DEE9FF">SET</span></span>
<span class="line"><span style="color: #D8DEE9FF">	entry_point_2 </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> entry_point</span></span>
<span class="line"><span style="color: #D8DEE9FF">WHERE</span></span>
<span class="line"><span style="color: #D8DEE9FF">	entry_point IS NOT </span><span style="color: #81A1C1">NULL</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">AND</span><span style="color: #D8DEE9FF"> entry_point_2 IS </span><span style="color: #81A1C1">NULL</span></span>
<span class="line"></span></code></pre>
<p>There are probably more performant ways to this (it took a whopping 20 minutes to execute this query), but it doesn't add locks to our table and is easy to write.</p>
<h2>Setting everything right</h2>
<p>So we're almost there. We've got identical columns, <code>entry_point</code> of type text and <code>entry_point_2</code> of type varchar(2048). We're going to rename these columns so that:</p>
<ul>
<li>
<p><code>entry_point</code> -&gt; <code>old_entry_point</code></p>
</li>
<li>
<p><code>entry_point_2</code> -&gt; <code>entry_point</code></p>
</li>
</ul>
<p>We can do this instantly like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">ALTER TABLE  </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">RENAME COLUMN entry_point TO old_entry_point</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">RENAME COLUMN entry_point_2 TO entry_point</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>We remove the trigger we've created:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">DROP TRIGGER </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>And in the end, altogether remove the <code>old_entry_point</code> column:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">ALTER TABLE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">DROP COLUMN </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">old_entry_point</span><span style="color: #ECEFF4">`</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> ALGORITHM</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">INSTANT</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Now we can create an index on the newly created <code>entry_point</code> column:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">ALTER TABLE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #D8DEE9FF">ADD INDEX </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences_entry_point_index</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">entry_point</span><span style="color: #ECEFF4">`</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">255</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> USING BTREE</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>And, of course, we also need to create the fulltext index we removed earlier. The thing is, we can only add such an index when nothing is written to the table. To fix this, we temporarily paused Laravel Horizon:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">php artisan horizon</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">pause</span></span>
<span class="line"></span></code></pre>
<p>Our queues are now paused so that nothing gets written to this table. We don't lose anything thanks to Horizon because the jobs we've added still exist, and they can be executed as soon as we restart Horizon again.</p>
<p>Now we can add the fulltext index as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">ALTER TABLE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">`</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">`</span></span>
<span class="line"><span style="color: #D8DEE9FF">ADD FULLTEXT INDEX </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">error_message_fulltext</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">exception_message</span><span style="color: #ECEFF4">`</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>And restart horizon:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">php artisan horizon</span><span style="color: #81A1C1">:continue</span></span>
<span class="line"></span></code></pre>
<h2>Conclusion</h2>
<p>A minor fix turned out to be more complicated than expected, but the result is what counts. And our page now got a lot faster. We'll start inviting beta testers soon. Interested? Contact us at support@flareapp.io.</p>
]]>
            </summary>
                                    <updated>2023-05-07T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Fetching data to render complex graphs in Flare]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/fetching-data-to-render-complex-graphs-in-flare" />
            <id>https://flareapp.io/fetching-data-to-render-complex-graphs-in-flare</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When tracking errors within your application, a visual indication of when errors are happing is essential in the debugging experience we're trying to provide.</p>
<p>Flare already had graphs on errors happening per project. Here's how that looks like:</p>
<p><img src="https://content.spatie.be/assets/flare/fetching-data-to-render-complex-graphs-in-flare/graphs1.png" alt="" /></p>
<p>Now, we've also added graphs per error. So you can easily see when a particular error start occurring, when it was spiking, and more. Here how such a graph looks like.</p>
<p><img src="https://content.spatie.be/assets/flare/fetching-data-to-render-complex-graphs-in-flare/graphs2.png" alt="" /></p>
<p>These graphs count the number of occurrences of each error in a project. Sometimes we have five occurrences for an error, but there are situations where we have 40k occurrences. Our database already does a terrific job of crunching all this data. But this still can take a while.</p>
<p>We wanted to keep Flare fast and, more importantly, have a single approach to how we did these calculations and smartly cached them.</p>
<h2>Requirements</h2>
<p>We have four points in our application where we show graphs:</p>
<ul>
<li>
<p>On the projects view, we show a graph of occurrences per project per day</p>
</li>
<li>
<p>On the project errors view, we show a graph of all occurrences for the project per minute scoped by the current search query</p>
</li>
<li>
<p>Also, on the project errors view, for each error, we show a graph of occurrences per day</p>
</li>
<li>
<p>On an error-specific page, the graph we mentioned above is also shown</p>
</li>
</ul>
<p>Let's see what we need:</p>
<ul>
<li>
<p>We should be able to get graphs for a single or multiple items</p>
</li>
<li>
<p>Graphs are per day, sometimes per minute</p>
</li>
<li>
<p>A graph can be changed by external parameters (like the search query)</p>
</li>
<li>
<p>We want everything blazingly fast ⚡️</p>
</li>
</ul>
<h2>Caching</h2>
<p>The best way to solve this is by caching graphs and using the cached values on the following requests. The only problem: Flare is a real-time product. You can have a graph of zero errors for the whole month, and suddenly due to some bug within your code, that graph could have hundreds of occurrences.</p>
<p>We don't want to wait for the next day to show you that these occurrences came in. You want to see these graphs in real time.</p>
<p>That's why we're using a combination of caching and live data. For all the graphs, we cache the data of the retention period you've got in your subscription except the current day. For the current day, we'll do live data crunching to calculate how many occurrences have passed by.</p>
<p>Such caching will work since new errors will only come in today, which is queried by the live statistics.</p>
<p>A complete cache bust should happen when an error is deleted, resolved, or unresolved because these actions are destructive to our data from the past. We'll need to regenerate them again on the subsequent request.</p>
<h2>The implementation</h2>
<p>We've constructed an abstract base class, <code>MultiOccurrenceStats</code>, from which three classes extend:</p>
<ul>
<li>
<p><code>ErrorOccurrenceStatsPerDay</code> for the error view and the project errors view</p>
</li>
<li>
<p><code>ProjectErrorOccurrenceStatsPerMinute</code> for the project errors view</p>
</li>
<li>
<p><code>ProjectErrorOccurrenceStatsPerDay</code> for the projects view</p>
</li>
</ul>
<p>The base class has a <code>fetch</code> method, allowing us to fetch data for multiple subjects(errors or projects). We return an array for each subject with as key a UNIX timestamp and as value the number of occurrences around that timestamp:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88"> * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88">  </span><span style="color: #81A1C1">int[]</span><span style="color: #616E88">  $subjectIds</span></span>
<span class="line"><span style="color: #616E88"> * </span><span style="color: #81A1C1">@return</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #81A1C1">int</span><span style="color: #616E88">, array&lt;int, int&gt;&gt;</span></span>
<span class="line"><span style="color: #616E88"> */</span></span>
<span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fetch</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectIds</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">CarbonImmutable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">start</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">CarbonImmutable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">end</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// ... </span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Each subclass should also implement a few methods:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">//provides the key to storing statistics for a subject</span></span>
<span class="line"><span style="color: #81A1C1">abstract</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cacheKey</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// The time the graph is cached</span></span>
<span class="line"><span style="color: #81A1C1">abstract</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cacheTtl</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// A query to fetch the statistics</span></span>
<span class="line"><span style="color: #81A1C1">abstract</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">performQuery</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Builder</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// The subject id in the query above. Please continue reading to know why we need this</span></span>
<span class="line"><span style="color: #81A1C1">abstract</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">querySubjectIdColumn</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string;</span></span>
<span class="line"></span></code></pre>
<p>Now we can call the <code>fetch</code> method with</p>
<ul>
<li>on a single subject like the project:</li>
</ul>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">fetch</span><span style="color: #ECEFF4">([</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">],</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">retentionStart</span><span style="color: #ECEFF4">(),</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">retentionEnd</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<ul>
<li>for multiple subjects like the errors in a project:</li>
</ul>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">fetch</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectIds</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">start</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">end</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>How is it implemented? First, we map our subject ids to the keys within our cache:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheKeys</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">array_map</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #81A1C1">fn</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">int</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">cacheKey</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectIds</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>We split the retention period into a cacheable and non-cacheable period:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachablePeriod</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCacheablePeriod</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">splitPeriodInCacheableAndNonCacheable</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">retentionPeriod</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// calls:</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">splitPeriodInCacheableAndNonCacheable</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">StatsPeriod</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">retentionPeriod</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">StatsPeriod</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">retentionPeriod</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">start</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">startOfDay</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #8FBCBB">CarbonImmutable</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">yesterday</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">endOfDay</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">StatsPeriod</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #8FBCBB">CarbonImmutable</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">startOfDay</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">retentionPeriod</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">end</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">endOfDay</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Now we want to check what's been cached and what isn't. We store a <code>CachedStats</code> object within our cache:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CachedStats</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88">  </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #81A1C1">int</span><span style="color: #616E88">, int&gt;  $stats</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">StatsPeriod</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">period</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This object indicates whether cached graphs exist for a specific subject and for which period these were cached. Then, we will split our subjects into two categories: subjects for which a correct cached period exists and subjects without cached graphs.</p>
<p>Notice the <code>cache()-&gt;many()</code> method. It queries all cache keys at once, which is a bit faster. Keys that do not exist will return <code>null</code>.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Support</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Collection</span><span style="color: #616E88">&lt;</span><span style="color: #81A1C1">int</span><span style="color: #616E88">, null&gt; $nonCachedSubjects */</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCachedSubjects</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">collect</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Support</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Collection</span><span style="color: #616E88">&lt;</span><span style="color: #81A1C1">int</span><span style="color: #616E88">, \App\Domain\Project\Stats\CachedStats&gt; $cachedSubjects */</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachedSubjects</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">collect</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">foreach</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">array_values</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">cache</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">many</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheKeys</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">i</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cached</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectIds</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">i</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cached</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">||</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cached</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">instanceof</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CachedStats</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">||</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cached</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">period</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">equals</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachablePeriod</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCachedSubjects</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachedSubjects</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cached</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>From this point, the individual classes which extend from this base class become important. Fetching the data for graphs is different depending on what's exactly needed. From now on, we'll implement the stats for errors on the projects' errors page.</p>
<p>This is the base query required for these stats:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #8FBCBB">DB</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">table</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">error_occurrences</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">selectSub</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">error_occurrences.error_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">subject_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">selectRaw</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">CAST(UNIX_TIMESTAMP(DATE_FORMAT(received_at, &quot;%Y-%m-%d 00:00:00&quot;)) * 1000 AS UNSIGNED) AS timestamp, count(*) as count</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">join</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">errors</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">error_occurrences.error_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">errors.id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">errors.status</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Status</span><span style="color: #81A1C1">::</span><span style="color: #D8DEE9FF">Open</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>This query is missing some parts. We're not grouping anything or scoping the query onto our subjects. Within the <code>fetch</code> method, another part is added:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">performQuery</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">groupBy</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">timestamp</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">subject_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">when</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCachedSubjects</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isNotEmpty</span><span style="color: #ECEFF4">(),</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">whereIn</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">querySubjectIdColumn</span><span style="color: #ECEFF4">(),</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCachedSubjects</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">keys</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">whereBetween</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">received_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">retentionPeriod</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">start</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">retentionPeriod</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">end</span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">when</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachedSubjects</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isNotEmpty</span><span style="color: #ECEFF4">(),</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">orWhere</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Builder</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">builder</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">whereIn</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">querySubjectIdColumn</span><span style="color: #ECEFF4">(),</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachedSubjects</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">keys</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">whereBetween</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">received_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCacheablePeriod</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">start</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCacheablePeriod</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">end</span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">orderBy</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">timestamp</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>These extra statements will:</p>
<ul>
<li>
<p>Group counted occurrences by their timestamp</p>
</li>
<li>
<p>Select for the non-cached subjects the whole retention period</p>
</li>
<li>
<p>Select the cached subjects for one day: today</p>
</li>
<li>
<p>Order the results by their timestamp</p>
</li>
</ul>
<p>Notice the <code>$this-&gt;querySubjectIdColumn()</code> call we discussed earlier. It selects occurrences based on the subject and will, in this case, be <code>error_occurrences.error_id</code>.</p>
<p>This massive query is now executed, and we group the results in a Laravel Collection by its subject:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">groupBy</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">subject_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">map</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Collection</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectStats</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectStats</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">mapWithKeys</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	    </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">object</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">row</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">row</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">timestamp</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">row</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">count</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">all</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>At this point, our flow splits into two based on whether a subject has cached values.</p>
<h3>The non-cached subjects</h3>
<p>We will first cache the freshly calculated counts so they can be reused in the next request. We'll start by filtering all the stats we've queried and only use stats before today which have not yet been cached:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheableStats</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">filter</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCachedSubjects</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">has</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">map</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">filterStatsForPeriod</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachablePeriod</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Calls:</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88"> * </span><span style="color: #81A1C1">@return</span><span style="color: #616E88"> </span><span style="color: #81A1C1">array</span><span style="color: #616E88">&lt;</span><span style="color: #81A1C1">int</span><span style="color: #616E88">, int&gt;</span></span>
<span class="line"><span style="color: #616E88"> */</span></span>
<span class="line"><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">filterStatsForPeriod</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #8FBCBB">StatsPeriod</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">period</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">min</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">period</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">start</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getPreciseTimestamp</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">max</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">period</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">end</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getPreciseTimestamp</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">filtered</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">foreach</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">timestamp</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">count</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">min</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">timestamp</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">timestamp</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">max</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">filtered</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">timestamp</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">count</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">filtered</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Then we're going to cache these stats if we have any:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheValues</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheableStats</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">mapWithKeys</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">cacheKey</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CachedStats</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachablePeriod</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheValues</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isNotEmpty</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">cache</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">putMany</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheValues</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">all</span><span style="color: #ECEFF4">(),</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">cacheTtl</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>In the end, we put all the stats we've queried within an array per subject:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCachedStats</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCachedSubjects</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">map</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">null</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">null</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[])</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">all</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<h3>The cached subjects</h3>
<p>For the cached subjects, we're going to take the cached stats and append the freshly added stats:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachedStats</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachedSubjects</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">map</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">CachedStats</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachedStats</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">has</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachedStats</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">stats</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">subjectId</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachedStats</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">stats</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">all</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>That + within the code might look weird. Why not use <code>array_merge()</code>? In this case, the problem with <code>array_merge</code> is that it removes the keys, which are our timestamps. Crucial information we want to keep.</p>
<p>The + operation between arrays won't lose these keys but is sometimes considered dangerous because when a key overlaps, the left-hand side value is used. That's why we've put the most recent data on the left-hand side of this expression. Should something go wrong, we'll always have the most up-to-date data.</p>
<p>The latest line of the <code>fetch</code> method looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">nonCachedStats</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cachedStats</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>We've successfully and efficiently calculated our stats!</p>
<h2>Future improvements</h2>
<p>We'll use this version for the following weeks. Some improvements can be made, though:</p>
<ul>
<li>
<p>Make the caching interval larger for minute stats. We could cache till an hour ago instead of the start of the day, thus reducing the query</p>
</li>
<li>
<p>Instead of requiring the exact cache period (start of retention period until yesterday), we could accept each cached period and then add the missing data in between when we're querying the most recent data</p>
</li>
<li>
<p>Make more use of lazy collections since we're working with a lot of data</p>
</li>
<li>
<p>When using arrays, pass them through with references. Which should eliminate some memory copies</p>
</li>
<li>
<p>Probably other stuff we can't think of right now 😀</p>
</li>
</ul>
<h2>In the end</h2>
<p>The Flare redesign is coming together. We hope to start inviting beta testers soon. Want to join this group? Send a mail to info@flareapp.io.</p>
<p>Now is the perfect time to try out Flare. You can <a href="https://flareapp.io">register here</a> for a free ten-day trial; no credit card is required.</p>
]]>
            </summary>
                                    <updated>2023-04-18T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[🐟 Introducing Flare Roulette™]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-flare-roulette" />
            <id>https://flareapp.io/introducing-flare-roulette</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Being a developer is hard. We're writing code throughout the day to find out at the end of the week that this code isn't working as expected. Sometimes, this can make us a bit emotional, we have a hard time figuring out what's happening, and we resort to measures beyond the non-programming folks' imagination, like talking to rubber ducks trying to find that one bug in our code.</p>
<p>We, as the developers of Flare, are convinced this should stop! That's why we're introducing Flare Roulette™ today. Let's make bug-fixing great again!</p>
<p>The best way to fix bugs within your code is to not talk to a rubber duck or get a mental breakdown where you want to smash your laptop against the wall. Are you going to ask an untrusty AI for anwers? No! The best way to fix bugs is by talking to people, talking with each other, and having the satisfaction of solving a bug together.</p>
<p>Flare Roulette™ is a new feature within the Flare application where everyone with the same exception will be thrown in a random chat roulette because solving an error with two is like a dream coming true.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-flare-roulette/r1.png" alt="r1" /></p>
<p>You'll be paired with another Artisan, and the both of you can find a solution to your shared problem.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-flare-roulette/r2.png" alt="r2" /></p>
<p>Can't find a solution together? No problem. By clicking the &quot;find another Artisan&quot; button, you'll be matched with another developer in despair with who you can hopefully find the answer.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-flare-roulette/r3.png" alt="r3" /></p>
<p>This feature will drastically reduce development time and raise bug-fixing satisfaction by 42%! What are you waiting for? Register now and start bug fixing 🎤</p>
]]>
            </summary>
                                    <updated>2023-04-01T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Optimizing Flare]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/optimizing-flare" />
            <id>https://flareapp.io/optimizing-flare</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Our whole team is currently working hard on the next version of Flare. We're completely redesigning the application and website, and it is an immense effort, but it will undoubtedly pay off in the future.</p>
<p>In this rewrite of Flare, we decided to move from Laravel resources to our own Laravel data package for sending data to the front end. Doing this not only adds the benefit of a fully typed response, but we also get TypeScript definitions for our resources without extra effort.</p>
<p>Once all resources were manually converted into data objects, we were excited to check if everything worked fine. Though we didn't hit on any exceptions or errors, some application pages felt extremely slow, too slow to be useful for our clients.</p>
<p>We quickly found that much of the response time was spent on the data objects. We started by optimizing the data objects using the lazy functionality, which allows loading certain parts of the data later on when the initial page is already sent. It made the pages faster, but we didn't want to stop there since we're completely redesigning the whole application.</p>
<p>The laravel-data package is fantastic to work with, but it also adds a lot of complexity when outputting data. In this blog post, we will look at how we've improved the performance of the package and, thus, the complete Flare application.</p>
<h2>New tools to play with</h2>
<p>To get started, I will introduce you to three tools you can use within your toolset to research performance problems: Xdebug, PHPBench, and QCacheGrind.</p>
<p>Xdebug is a PHP extension that provides debugging and profiling capabilities. It is most famous for its complicated configuration, which may slow down your whole application. Luckily, Xdebug version 3 made extensive progress on this part, and setting up Xdebug is now relatively easy.</p>
<p>PHPBench is a benchmarking tool for PHP that can help you measure the performance of your code. It allows you to create test cases and run them multiple times to get a notion of the speed of your code.</p>
<p>Lastly, QCacheGrind is an application allowing you to look at the profiles generated by Xdebug visually. QCacheGrind is available for MacOS and Windows. If you're on Linux, look at KCachegrind, which is almost the same.</p>
<h2>Getting a baseline</h2>
<p>To start, we need to be able to test if the changes we make to our code have a positive or negative impact on the performance. The best way to do this is by creating a few benchmarks using PHPBench. These benchmarks will be run multiple times, and the average time this take can be used as a metric to see if we've made a difference by updating the code.</p>
<p>PHPBench works a lot like PHPUnit. You create a few benchmark files (like tests) with benchmark cases (like test cases). The PHPBench command will then execute these files like the PHPUnit command would. Only it executes the cases multiple times and tracks how long it takes.</p>
<p>We get started as usual by installing PHPBench:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">composer </span><span style="color: #81A1C1">require</span><span style="color: #D8DEE9FF"> phpbench</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">phpbench </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">dev</span></span>
<span class="line"></span></code></pre>
<p>Next, we create a <code>phpbench.json</code> file which configures PHPbench:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">schema</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">:</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">./vendor/phpbench/phpbench/phpbench.schema.json</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">runner.bootstrap</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">vendor/autoload.php</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">runner.path</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">benchmarks</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The <code>runner.path</code> option defines the directory where our benchmarks are located, so we create a new directory for them:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">mkdir benchmarks</span></span>
<span class="line"></span></code></pre>
<p>The last thing to do is create the benchmark. We call this file <code>ExampleBench.php</code>. Notice the <code>Bench</code> suffix. Using this suffix is essential because, otherwise, PHPBench won't execute the file. Within the <code>ExampleBench.php</code>, we add an <code>ExampleBench </code> class:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ExampleBench</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Let's create a first benchmark:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> PhpBench</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Attributes</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Iterations</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> PhpBench</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Attributes</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Revs</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ExampleBench</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    #[Revs</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">500</span><span style="color: #ECEFF4">),</span><span style="color: #D8DEE9FF"> Iterations</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">5</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">benchPow</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">pow</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>As you can see, we will benchmark how long it takes to calculate 2^10. The benchmark case we've added is called <code>benchPow</code>. Notice the <code>bench</code> keyword prefixed, which is required for PHPBench to find the benchmark case.</p>
<p>We've also added two attributes to the method:</p>
<p>Revs, short for revolutions, refers to the number of times the benchmark is executed consecutively within a single time measurement. The more revolutions, the more accurate the result. In this case, we will calculate 2^10 a whopping 500 times!</p>
<p>In an ideal world, we're done and can start measuring. But running these revolutions multiple times is safer and more correct to ensure our measurements are stable and don't differ too much.</p>
<p>We have five iterations of 500 revolutions so that the pow function will be executed for 5 * 500 = 2500 times.</p>
<p>Now it is time to run PHPBench:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">vendor</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">bin</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">phpbench run  </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">report</span><span style="color: #81A1C1">=default</span></span>
<span class="line"></span></code></pre>
<p>And we get the following result:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">PHPBench </span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">9</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> running benchmarks</span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">#standwithukraine</span></span>
<span class="line"><span style="color: #D8DEE9FF">with configuration file</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Users</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">ruben</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Spatie</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">data</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">phpbench</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">json</span></span>
<span class="line"><span style="color: #D8DEE9FF">with PHP version </span><span style="color: #B48EAD">8</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> xdebug ✔</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> opcache ❌</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">\ExampleBench</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    benchPow</span><span style="color: #81A1C1">..............................</span><span style="color: #81A1C1">..</span><span style="color: #D8DEE9FF">I4 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">038</span><span style="color: #D8DEE9FF">μs </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±3</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">33</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">Subjects: </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> Assertions</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> Failures</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> Errors</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span></span>
<span class="line"><span style="color: #81A1C1">+------+--------------+----------+-----+------+------------+----------+--------------+----------------+</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> iter </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchmark    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> subject  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> set </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> revs </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> mem_peak   </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> time_avg </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> comp_z_value </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> comp_deviation </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">+------+--------------+----------+-----+------+------------+----------+--------------+----------------+</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPow </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">036</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">58</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">5</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">26</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPow </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">038</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">00</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">00</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPow </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">038</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">00</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">00</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPow </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">038</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">00</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">00</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPow </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">040</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">58</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">5</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">26</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">+------+--------------+----------+-----+------+------------+----------+--------------+----------------+</span></span>
<span class="line"></span></code></pre>
<p>It looks like 0.038μs is the best average we have. Let's see what's the fastest:</p>
<ol>
<li>
<p>2^20</p>
</li>
<li>
<p>2^10 * 2^10</p>
</li>
</ol>
<p>Our benchmark now will look like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ExampleBench</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    #[Revs</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">500</span><span style="color: #ECEFF4">),</span><span style="color: #D8DEE9FF"> Iterations</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">5</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">benchPow</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">pow</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #B48EAD">20</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    #[Revs</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">500</span><span style="color: #ECEFF4">),</span><span style="color: #D8DEE9FF"> Iterations</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">5</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">benchPowPow</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">pow</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">pow</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Running PHPBench gives the following results:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">PHPBench </span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">9</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> running benchmarks</span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">#standwithukraine</span></span>
<span class="line"><span style="color: #D8DEE9FF">with configuration file</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Users</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">ruben</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Spatie</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">data</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">phpbench</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">json</span></span>
<span class="line"><span style="color: #D8DEE9FF">with PHP version </span><span style="color: #B48EAD">8</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> xdebug ✔</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> opcache ❌</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">\ExampleBench</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    benchPow</span><span style="color: #81A1C1">..............................</span><span style="color: #81A1C1">..</span><span style="color: #D8DEE9FF">I4 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">039</span><span style="color: #D8DEE9FF">μs </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±3</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">78</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    benchPowPow</span><span style="color: #81A1C1">...........................</span><span style="color: #81A1C1">..</span><span style="color: #D8DEE9FF">I4 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">060</span><span style="color: #D8DEE9FF">μs </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±11</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">25</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">Subjects: </span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> Assertions</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> Failures</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> Errors</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span></span>
<span class="line"><span style="color: #81A1C1">+------+--------------+-------------+-----+------+------------+----------+--------------+----------------+</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> iter </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchmark    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> subject     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> set </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> revs </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> mem_peak   </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> time_avg </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> comp_z_value </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> comp_deviation </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">+------+--------------+-------------+-----+------+------------+----------+--------------+----------------+</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPow    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">040</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">27</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">01</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPow    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">042</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">60</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">6</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">06</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPow    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">038</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">07</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">4</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">04</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPow    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">038</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">07</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">4</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">04</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPow    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">040</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">27</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">01</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPowPow </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">076</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">47</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">16</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">56</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPowPow </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">072</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">93</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">+</span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">43</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPowPow </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">060</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">71</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">7</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">98</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPowPow </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">060</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">71</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">7</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">98</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> ExampleBench </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> benchPowPow </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">500</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">812</span><span style="color: #ECEFF4">,</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">b </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">058</span><span style="color: #D8DEE9FF">μs  </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">98</span><span style="color: #D8DEE9FF">σ       </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">11</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">04</span><span style="color: #81A1C1">%</span><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">+------+--------------+-------------+-----+------+------------+----------+--------------+----------------+</span></span>
<span class="line"></span></code></pre>
<p>So basically, it costs 50% more time to calculate two <code>pow(2, 10)</code> functions than one <code>pow(2, 20)</code>, that's quite useful!</p>
<p>Now back to laravel-data, we want to know how fast we can create a data object and how fast we can transform it into a JSON object. We can describe this as follows:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DataBench</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	#[Revs</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">500</span><span style="color: #ECEFF4">),</span><span style="color: #D8DEE9FF"> Iterations</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">benchDataCreation</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">MultiNestedData</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">from</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nested</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">simple</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Hello</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nestedCollection</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">simple</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Flare</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">simple</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">is</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">simple</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">awesome</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    #[Revs</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">500</span><span style="color: #ECEFF4">),</span><span style="color: #D8DEE9FF"> Iterations</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">benchDataTransformation</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MultiNestedData</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NestedData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SimpleData</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Hello</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DataCollection</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">NestedData</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NestedData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SimpleData</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Flare</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)),</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NestedData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SimpleData</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">is</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)),</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NestedData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SimpleData</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">awesome</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toArray</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    #[Revs</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">500</span><span style="color: #ECEFF4">),</span><span style="color: #D8DEE9FF"> Iterations</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">benchDataCollectionCreation</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">collection</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Collection</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">times</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #B48EAD">15</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nested</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">simple</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Hello</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nestedCollection</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">simple</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Flare</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">simple</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">is</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">simple</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">awesome</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">all</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">MultiNestedData</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">collection</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">collection</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    #[Revs</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">500</span><span style="color: #ECEFF4">),</span><span style="color: #D8DEE9FF"> Iterations</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">benchDataCollectionTransformation</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">collection</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Collection</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">times</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #B48EAD">15</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MultiNestedData</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NestedData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SimpleData</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Hello</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)),</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DataCollection</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">NestedData</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NestedData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SimpleData</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Flare</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)),</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NestedData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SimpleData</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">is</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)),</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NestedData</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SimpleData</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">awesome</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)),</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">all</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">collection</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MultiNestedData</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">collection</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">collection</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">collection</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toArray</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>In the first benchmark, we're creating a data object using arrays. In the second one, we created an object as efficiently as possible by manually defining it and then transforming it to JSON.</p>
<p>We also added two cases where we do the same, but instead of using data objects, we benchmark using data collections of multiple data objects.</p>
<p>Now running PHPBench will fail because the laravel-data package depends on some Laravel functionality. Luckily Laravel provides excellent testing infrastructure, which we can use within our benchmark. We do this by using the <code>CreatesApplication</code> trait, which is also present in the base Laravel test case:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Tests</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CreatesApplication</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DataBench</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CreatesApplication</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">createApplication</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// The benchmarks</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">   </span></span>
<span class="line"></span></code></pre>
<p>We need another update because we're benchmarking a laravel package, not a laravel application. This means we must use the <code>CreatesApplication</code> trait from the <code>orchestra/testbench</code> package, which is used to test Laravel packages. And we also need to specify the laravel-data service provider to boot up the package:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Orchestra</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Testbench</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Concerns</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CreatesApplication</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DataBench</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CreatesApplication</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">createApplication</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getPackageProviders</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #8FBCBB">LaravelDataServiceProvider</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// The benchmarks</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"></span></code></pre>
<p>Great, we now have some benchmarks ready to measure the speed of our code!</p>
<h2>Getting started with Xdebug</h2>
<p>Next up, we need to profile our code. When we're profiling our code, we will run it a bit differently than like would typically. When profiling the PHP process will write down each function call we make and also keep track of how long it takes to run the function. Ultimately, all this information will be written into a file we can analyze.</p>
<p>To get this working, we need to install and enable Xdebug. You can find the instructions <a href="https://xdebug.org/docs/install">here</a>.</p>
<p>For me, on a Mac, it was as simple as this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">sudo pecl install xdebug</span></span>
<span class="line"></span></code></pre>
<p>We also need to update or create the Xdebug .ini file:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">xdebug</span><span style="color: #ECEFF4">]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">xdebug</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">mode</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">profile</span></span>
<span class="line"><span style="color: #D8DEE9FF">xdebug</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">start_with_request</span><span style="color: #81A1C1">=yes</span></span>
<span class="line"></span></code></pre>
<p>On my Mac, I've put this file here: <code>nano /opt/homebrew/etc/php/8.2/conf.d/xdebug.ini</code>.</p>
<p>We configure two options:</p>
<ul>
<li>
<p><code>xdebug.mode=profile</code> to enable profiling in Xdebug.</p>
</li>
<li>
<p><code>xdebug.start_with_request=yes</code> to start profiling with PHPBench. When you're done with profiling, don't forget to set this to <code>off</code> or <code>trigger</code>. Otherwise, all your PHP applications will take ages to load.</p>
</li>
</ul>
<p>We have our benchmarks, and we have our profiler. Now we only need to combine those two, and then we can discover why our code is so slow!</p>
<h2>Profiling the code</h2>
<p>We will rerun our benchmarks, but this time we will also profile the benchmarks. We can do this with the following command:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">vendor</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">bin</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">phpbench xdebug</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">profile</span></span>
<span class="line"></span></code></pre>
<p>Which gives the following output:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">PHPBench </span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">9</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> running benchmarks</span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">#standwithukraine</span></span>
<span class="line"><span style="color: #D8DEE9FF">with configuration file</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Users</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">ruben</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Spatie</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">data</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">phpbench</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">json</span></span>
<span class="line"><span style="color: #D8DEE9FF">with PHP version </span><span style="color: #B48EAD">8</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> xdebug ✔</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> opcache ❌</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">\DataBench</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    benchDataCreation</span><span style="color: #81A1C1">.....................</span><span style="color: #81A1C1">..</span><span style="color: #D8DEE9FF">I0 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo10</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">762</span><span style="color: #D8DEE9FF">ms </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">00</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    benchDataTransformation</span><span style="color: #81A1C1">...............</span><span style="color: #81A1C1">..</span><span style="color: #D8DEE9FF">I0 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">095</span><span style="color: #D8DEE9FF">ms </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">00</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    benchDataCollectionCreation</span><span style="color: #81A1C1">............</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">I0 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo159</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">879</span><span style="color: #D8DEE9FF">ms </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">00</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    benchDataCollectionTransformation</span><span style="color: #81A1C1">......</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">I0 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo13</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">952</span><span style="color: #D8DEE9FF">ms </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">00</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">Subjects: </span><span style="color: #B48EAD">4</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> Assertions</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> Failures</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> Errors</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span></span>
<span class="line"></span>
<span class="line"><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">profile</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">s</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> generated</span><span style="color: #81A1C1">:</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Users</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">ruben</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Spatie</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">data</span><span style="color: #81A1C1">/</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">phpbench</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">xdebug</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">profile</span><span style="color: #81A1C1">/</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF">fcb020036c8d7e8efdcddd4dbd66b92</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cachegrind</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">gz</span></span>
<span class="line"><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Users</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">ruben</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Spatie</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">data</span><span style="color: #81A1C1">/</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">phpbench</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">xdebug</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">profile</span><span style="color: #81A1C1">/</span><span style="color: #B48EAD">8</span><span style="color: #D8DEE9FF">deba1dcce573e1bf818772ac7a5ace0</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cachegrind</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">gz</span></span>
<span class="line"><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Users</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">ruben</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Spatie</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">data</span><span style="color: #81A1C1">/</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">phpbench</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">xdebug</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">profile</span><span style="color: #81A1C1">/</span><span style="color: #B48EAD">06</span><span style="color: #D8DEE9FF">d049dacb2a7a3809e069c6c8289e02</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cachegrind</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">gz</span></span>
<span class="line"><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Users</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">ruben</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Spatie</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">data</span><span style="color: #81A1C1">/</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">phpbench</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">xdebug</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">profile</span><span style="color: #81A1C1">/</span><span style="color: #B48EAD">41</span><span style="color: #D8DEE9FF">c9fe618b93431786fff90b1d624b82</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">cachegrind</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">gz</span></span>
<span class="line"></span></code></pre>
<p>Running the benchmark suite now takes way longer than before. In the end, we have a new directory with our profiles:</p>
<ul>
<li>
<p>.phpbench/xdebug-profile/3fcb020036c8d7e8efdcddd4dbd66b92.cachegrind.gz</p>
</li>
<li>
<p>.phpbench/xdebug-profile/8deba1dcce573e1bf818772ac7a5ace0.cachegrind.gz</p>
</li>
<li>
<p>.phpbench/xdebug-profile/06d049dacb2a7a3809e069c6c8289e02.cachegrind.gz</p>
</li>
<li>
<p>.phpbench/xdebug-profile/41c9fe618b93431786fff90b1d624b82.cachegrind.gz</p>
</li>
</ul>
<p>Each file corresponds to a benchmark case within your benchmark suite. The best way to find out which file belongs to which benchmark is to look at the execution order of benchmark cases and match them with the files in the order they were created.</p>
<p>At the moment, all files are compressed. Let us decompress them:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">gzip </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">decompress </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">phpbench</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">xdebug</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">profile</span><span style="color: #616E88">/*</span></span>
<span class="line"></span></code></pre>
<h2>Analyzing</h2>
<p>It is finally time to find out why our code is slow. This is also the most complicated step in the process, and there is no guide to quickly finding a bottleneck. You should have a good understanding of your code and be able to see strange behavior within profiles. Some things that might stand out:</p>
<ul>
<li>
<p>functions that shouldn't be executed</p>
</li>
<li>
<p>functions that are executed too much</p>
</li>
<li>
<p>functions that take longer than expected</p>
</li>
</ul>
<p>Let's open a profile where we create a collection of data objects:</p>
<p><img src="https://content.spatie.be/assets/flare/optimizing-flare/qcachegrind1.png" alt="" /></p>
<p>First, we're going to configure QCacheGrind a bit more. Go to:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">QCacheGrind </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Preferences</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Source</span><span style="color: #D8DEE9FF"> Annotations</span></span>
<span class="line"></span></code></pre>
<p>And add the path where your code is situated. This allows QCacheGrind to give some hints with code examples of which code is slow.</p>
<p><img src="https://content.spatie.be/assets/flare/optimizing-flare/qcachegrind2.png" alt="" /></p>
<p>The application has three panels:</p>
<ul>
<li>
<p>On the left: the functions which were called sorted by the lengthiest call</p>
</li>
<li>
<p>On the right-top: the callers of a selected function</p>
</li>
<li>
<p>On the right-bottom: for a selected function, the functios called</p>
</li>
</ul>
<p>On the left side, we see all the functions called. The Self metric shows how long it took to call a function, the Incl. metric shows how long the function and all the functions it called took. So the PHPBench function, for example, takes 99.93% of the time Incl. but it spends only 0.01% of the time within the function. All the other time is used to call other functions, our benchmarks with data creation.</p>
<p>Immediately we see something bizarre. We spend 24.34% of the time in <code>Illuminate\Container\Container-&gt;resolve()</code>. Let's click on that function and see what's happening:</p>
<p><img src="https://content.spatie.be/assets/flare/optimizing-flare/qcachegrind3.png" alt="" /></p>
<p>This function has been called by <code>Illuminate\Foundation\Application-&gt;resolve()</code>. Let's click on that one. We see <code>Illuminate\Container\Container-&gt;make()</code> as the function, usually calling the <code>resolve</code> function. We keep on clicking on the most called functions until we enter the <code>helpers.php</code> file:</p>
<p><img src="https://content.spatie.be/assets/flare/optimizing-flare/qcachegrind4.png" alt="" /></p>
<p>From this data, we can see that we're resolving a lot from the Laravel container:</p>
<ul>
<li>
<p><code>Data::from</code> 67500 items</p>
</li>
<li>
<p><code>DataPipeline in a closure (line 75)</code> -&gt; 225000 items</p>
</li>
<li>
<p><code>DataPipeline in a closure (line 69)</code> -&gt; 187500 items</p>
</li>
</ul>
<p>What's exactly happening here? Let's take a look at the <code>DataPipeline</code> code:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Collection</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">LaravelData</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Normalizers</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Normalizer</span><span style="color: #81A1C1">[]</span><span style="color: #616E88"> $normalizers */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">normalizers</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">array_map</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #81A1C1">fn</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">|</span><span style="color: #8FBCBB">Normalizer</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">normalizer</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #88C0D0"> is_string</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">normalizer</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">?</span><span style="color: #88C0D0"> app</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">normalizer</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">:</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">normalizer</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">normalizers</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">LaravelData</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">DataPipes</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">DataPipe</span><span style="color: #81A1C1">[]</span><span style="color: #616E88"> $pipes */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">pipes</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">array_map</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #81A1C1">fn</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #ECEFF4">|</span><span style="color: #8FBCBB">DataPipe</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">pipe</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #88C0D0"> is_string</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">pipe</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">?</span><span style="color: #88C0D0"> app</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">pipe</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">:</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">pipe</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">pipes</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Other code, hidden to keep things a bit easier to read</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This code doesn't look like a performance problem. Yes, we're creating these normalizers and pipes from the container, but as long as this method isn't being executed that many times there won't be a problem in the end. This process should happen somewhere.</p>
<p>Let's find out from where this code is called:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">mixed</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...$</span><span style="color: #D8DEE9">payloads</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">BaseData</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">properties</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Collection</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">foreach</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payloads</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">BaseData</span><span style="color: #616E88"> $class */</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">pipeline</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">pipeline</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">foreach</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">class</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">normalizers</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">normalizer</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">pipeline</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">normalizer</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">normalizer</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">foreach</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">pipeline</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">using</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">payload</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">key</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">properties</span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">key</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">value</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Other code, hidden to keep things a bit easier to read</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>That could be better. We create the pipeline for each data object we're constructing, and thus, we resolve all the pipes and normalizers from the container each time. Every time again, this is not required as the pipeline is statically constructed for a data object and thus the same for all objects.</p>
<p>For a few objects, this will be fine. But since we have collections of the same objects (sometimes more than 500), we hit a huge performance problem.</p>
<p>We now cache these pipelines in version 3.2 of laravel-data and add some other performance improvements. This should do a lot for our performance! Finally, let's rerun the benchmarks.</p>
<p>Before performance optimizations:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">benchData</span><span style="color: #81A1C1">..............................</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">I1 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo293</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">376</span><span style="color: #D8DEE9FF">μs </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">79</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">benchDataCollection</span><span style="color: #81A1C1">.....................</span><span style="color: #D8DEE9FF">I1 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo5</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">619</span><span style="color: #D8DEE9FF">ms </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">48</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<p>After performance optimizations:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">benchData</span><span style="color: #81A1C1">..............................</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">I1 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo125</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">731</span><span style="color: #D8DEE9FF">μs </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">57</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">benchDataCollection</span><span style="color: #81A1C1">.....................</span><span style="color: #D8DEE9FF">I1 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> Mo2</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">111</span><span style="color: #D8DEE9FF">ms </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">±0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">82</span><span style="color: #81A1C1">%</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<p>For plain data objects, we've improved performance by 57%, and for collections of data, by 62%. That's huge!</p>
<h2>In the end</h2>
<p>With a few hours of work, we drastically reduced the performance penalty on Flare. Profiling the most critical parts of your application can be valuable, but it is not that easy!</p>
<p>We keep redesigning and refactoring Flare for the next weeks and hope to launch soon! You can find a preview of our new design <a href="https://flareapp.io/blog/48-a-preview-of-our-upcoming-redesign">here</a>. Are you interested in beta testing the new version of Flare? Please send us an email at support@flareapp.io.</p>
]]>
            </summary>
                                    <updated>2023-03-27T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[A preview of our upcoming redesign]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/a-preview-of-our-upcoming-redesign" />
            <id>https://flareapp.io/a-preview-of-our-upcoming-redesign</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Flare was launched almost four years ago. In human life, that's not too long, but for a service, that's a lifetime. Over the years, we had quite some ideas on how to improve the user experience for Flare. We hope you'll be happy to know that for the past few months, our team has been working hard on a total redesign of the service. By rethinking and streamlining the whole process, we hope to have created lots of improvements you'd like. Launch is planned in <strong>May</strong>.</p>
<p><strong>Homepage</strong><br/>Our complete marketing site has a fresh new coat of paint. We changed the dark purple background to a lighter variant.</p>
<p><img src="https://content.spatie.be/assets/flare/a-preview-of-our-upcoming-redesign/homepage.jpg" alt="screenshot" /></p>
<p><strong>Docs</strong><br/>We have rewritten the docs to be updated with the upcoming changes.</p>
<p><img src="https://content.spatie.be/assets/flare/a-preview-of-our-upcoming-redesign/docs.jpg" alt="screenshot" /></p>
<p><strong>Ignition</strong><br/>The Ignition landing page has been updated and Ignition itself has also been updated to align with the latest Laravel 10 style.</p>
<p><img src="https://content.spatie.be/assets/flare/a-preview-of-our-upcoming-redesign/ignition.jpg" alt="screenshot" /></p>
<p><strong>Platform</strong><br/>We didn't only redesign the marketing pages, but the entire application. Here are a few screens.</p>
<p><img src="https://content.spatie.be/assets/flare/a-preview-of-our-upcoming-redesign/login.jpg" alt="screenshot" /></p>
<p>Here's what a list of projects looks like.</p>
<p><img src="https://content.spatie.be/assets/flare/a-preview-of-our-upcoming-redesign/projects.jpg" alt="screenshot" /></p>
<p>When creating a project, you'll get improved installation instructions.</p>
<p><img src="https://content.spatie.be/assets/flare/a-preview-of-our-upcoming-redesign/installation.jpg" alt="screenshot" /></p>
<p>The error detail page has been built from the ground up.</p>
<p><img src="https://content.spatie.be/assets/flare/a-preview-of-our-upcoming-redesign/error.jpg" alt="screenshot" /></p>
<p>From the error details, you can quickly jump to the new insights pages. You can see a list of URLs where the error occurred or a list of users who might have seen this error. In this case, it was only me, phew!</p>
<p><img src="https://content.spatie.be/assets/flare/a-preview-of-our-upcoming-redesign/users.jpg" alt="screenshot" /></p>
<h2>In closing</h2>
<p>We hope that you like this preview. Only a few screens are shown, but we're giving our entire UI this treatment. We're not there yet, and more work has to be done, but we're confident we'll launch the redesign in May.</p>
<p>This redesign will also be the starting point of new features we'll add after launch.</p>
<p>Flare is one of the best error trackers for Laravel and PHP projects. We display the most important aspects of issues that happened in your production environment in our beautiful UI. For most issues, Flare will suggest solutions and related documentation. This way, you can fix your issue in no time.</p>
<p>Now is the perfect time to try out Flare. You can <a href="https://flareapp.io">register here</a> for a free ten-day trial; no credit card is required.</p>
]]>
            </summary>
                                    <updated>2023-03-08T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Fixing nested validation in Laravel]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/fixing-nested-validation-in-laravel" />
            <id>https://flareapp.io/fixing-nested-validation-in-laravel</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Since the early days, Laravel includes an excellent validator class. It allows you to check if the input provided to your application is valid according a set of rules and follows a specific format. Laravel's validator works on any arbitrary array of data but it's typically used for request validation. You can check if an email is valid, a password is confirmed, a file is a pdf, a string is not longer than a certain amount of characters, and so much more.</p>
<p>Validating input in Laravel is incredibly powerful and quick to set up. It's always been one of our favorite Laravel features. But, a few strange edge cases can be found where the validator isn't working as expected. When developing a Flare feature, we encountered one of these oddities and found a nice way to work around it.</p>
<p>Suppose that we're trying to validate a basic array with a nested <code>team</code> property:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p><em>(The example above is abbreviated for better readability and clarity)</em></p>
<p>We could now write a <a href="https://laravel.com/docs/master/validation#form-request-validation"><code>FormRequest</code></a> to validate this data automatically or manually validate the array using the <code>Validator</code> facade in the controller. To keep things simple, we'll stick to using the validator class directly:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">rules</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">string</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">array</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Rule</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">teams</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Rule</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">in</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">project_manager</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">boss</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])],</span></span>
<span class="line"><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">validator</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Validator</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">rules</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>We can now check if our data array (or &quot;payload&quot;) is valid:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">validator</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">passes</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// Returns true, data is valid</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">validator</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">messages</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// An empty message bag, great!</span></span>
<span class="line"></span></code></pre>
<p>When passing an invalid payload array as our input, the validator will fail and provide us with a couple of validation messages:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2023</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">ceo</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">validator</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Validator</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">rules</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">validator</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">passes</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// returns false!</span></span>
<span class="line"></span></code></pre>
<p>The error bag contains the following validation messages:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">team.id</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">The selected team.id is invalid.]</span></span>
<span class="line"><span style="color: #A3BE8C">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">team</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">role</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C"> =&gt; [</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">The selected team</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">role is invalid</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">]</span></span>
<span class="line"><span style="color: #A3BE8C">]</span></span>
<span class="line"></span></code></pre>
<p>Great, so up until now, everything is working as expected. We can validate both root level properties and nested properties in the <code>team</code> array.</p>
<p>Let's make things more complicated by allowing the <code>team</code> property to be <code>nullable</code>. Maybe your application allows for users without teams, so the team property can also be <code>null</code>.</p>
<p>Let's update our rules by adding the <code>nullable</code> rule:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">string</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nullable</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">array</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Rule</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">teams</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)],</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Rule</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">in</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">project_manager</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">boss</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])],</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>When testing our two previous example payload again, everything still works as expected: the valid example payload passes and the second example contains errors so the validator fails. However, let's now test our new feature where we don't have a team at all:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>The <code>team</code> property is <code>nullable</code> so this payload should not give us validation errors. Let's validate:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">team.id</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">The selected team.id is invalid.]</span></span>
<span class="line"><span style="color: #A3BE8C">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">team</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">role</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C"> =&gt; [</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">The selected team</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">role is invalid</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">]</span></span>
<span class="line"><span style="color: #A3BE8C">]</span></span>
<span class="line"></span></code></pre>
<p><em>Wait what?!</em> For some reason, the validator ran the <code>team.id</code> and <code>team.role</code> rules even though we wrote a rule that the entire <code>team</code> array is allowed be null, so what happened here?</p>
<p>The validator did its job correctly, albeit a bit bizarre. We wrote three rules for the <code>team</code> array. The first one tells the validator that <code>team</code> should be an array but can also be null. This rule succeeds with our last example payload. The other two rules require a value to be in a specific format. And these fail as the value is always <code>required</code>.</p>
<p>Laravel's validator is built in a way where each rule exists on its own. This means the two <code>team.*</code> rules have no context of the first rule indicating that the <code>team</code> array can be null. So they get executed as they would typically, resulting in two error messages.</p>
<p>How can we fix this?</p>
<h2>Nullable all the way</h2>
<p>We could rewrite our rules like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">string</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nullable</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">array</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nullable</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Rule</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">teams</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nullable</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Rule</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">in</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">project_manager</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">boss</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])],</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>Now when validating the following:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>Great! No validation messages, but what if we provide a payload like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>Also no validation messages, but we expect one since a team always needs an id and role. There are better approaches to writing rules like this.</p>
<h2>Better requiring</h2>
<p>We could rewrite our require rules smarter by explicitly telling when a specific value is required:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">string</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nullable</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">array</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required_with:team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Rule</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">teams</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)],</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required_with:team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Rule</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">in</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">project_manager</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">boss</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])],</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>When validating:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>No messages. Up to the next one:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>We now get the following message:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">team.role</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">The team.role field is required when team is present.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>Marvelous, we succeeded in writing the correct rules! But from now on, we must always use the <code>required_with:team</code> rule instead of the <code>required</code> rule and remember this for new properties to be added in the future.</p>
<p>We also need to be careful. If someone decides to change the name of the team property, then all our rules need to be updated. We only have two rules now, but as an application grows, these numbers tend to rise rapidly, and a mistake is quickly made.</p>
<p>Lastly, what if we also want to nest another array within the team array like the payload bellow, where we also want to be able to (optionally) disable or enable extra features per team:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">features</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">github</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">jira</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>And to make things more complicated, you should also be able to provide no features (in this case, defaults could be used):</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>These were the best rules I could come up with:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">string</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nullable</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">array</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required_with:team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Rule</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">teams</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required_with:team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Rule</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">in</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">project_manager</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">boss</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.features</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">nullable</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required_with:team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">array</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.features.github</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required_with:team.features</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">boolean</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team.features.jira</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">required_with:team.features</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">boolean</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>But these are not 100% watertight since the latest payload we'v constructed without the features array will result in the following validation message:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">team.features</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">	  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">The team.features field is required when team is present.</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>The <code>required_with</code> rules can fix our problem but have a few disadvantages. Is there another way?</p>
<h2>Fixing the issue once and for all</h2>
<p>In Flare, we don't write validation rules ourselves. One of our packages called <a href="https://github.com/spatie/laravel-data">laravel-data</a> automatically generates them.</p>
<p>Let's revisit our previous example, but we now structure our data using data objects. Which has as an added benefit a stricter type structure we can use in the backend. We stop using untyped arrays with data and have objects with typed properties.</p>
<p>The laravel-data package can also generate TypeScript definitions based upon the types within our data objects, so our frontend code is now also typed exactly like our backend. Cool!</p>
<p>These are the data objects we've created based on the rules we decided on earlier:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">UserData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Data</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">name</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">TeamData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">TeamData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Data</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        #[Exists</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">teams</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">int</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">id</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        #[In</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">project_manager</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">boss</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">role</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">TeamFeaturesData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">features</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">TeamFeaturesData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Data</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">github</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">jira</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Within our controller, we inject the <code>User</code> data object as follows:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">UpdateUserController</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__invoke</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">User</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">UserData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">UpdateOrCreateUserAction</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">updateOrCreateUserAction</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">updateOrCreateUserAction</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">data</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">flash</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">User updated!</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">redirect</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">back</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>As you can see, we also inject the user model we're updating and the action (a class with business logic) to perform the update within the database.</p>
<p>Due to the nature of laravel-data, when injected in a controller, the data object will validate the request input before creating itself. Let's take a look at the previous inputs we provided to the validator:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>No validation messages 👍</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>Also no validation messages 🥳</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">role</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">engineer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">features</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">github</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">jira</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>No validation messages. We fixed our initial problem! 🎉</p>
<p>At this point in time, you start to wonder, is the validation working? Let's try this one out:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Ruben Van Assche</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">team</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// Validation messages:</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">team.role</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">The team.role field is required when team is present.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>It looks like everything is still validated as expected.</p>
<h2>Internals</h2>
<p>Let's go quickly over the internals of laravel-data. Until a few weeks ago, laravel-data worked precisely like our first approach by making all rules nullable in nested arrays. Though working, there were better solutions than this. So we completely rewrote the validation logic for laravel-data's third version.</p>
<p>Instead of generating rules before the payload is provided, laravel-data has a sort of JIT rule generator. It will generate rules based on the definition in the data class and the payload passed in.</p>
<p>For example, when we have a data object like <code>UserData</code>, all non-nested data property rules will be generated. The nested <code>TeamData</code> is nullable, so it will only start doing the same and thus generate rules for itself when we find a key <code>team</code> within the input payload that is not null.</p>
<p>If you're more interested in the internal workings of the validation rule generation, then take a look over <a href="https://github.com/spatie/laravel-data/blob/main/src/Resolvers/DataValidationRulesResolver.php">here</a>, where we generate validation rules for data objects just in time.</p>
<h2>Closing</h2>
<p>Laravel's validator is incredible, but sometimes it needs some help. Expect more articles like this on the Flare blog in the coming months and a new coat of paint for our website and application. Want a sneak peek? Mail us at support@flareapp.io.</p>
]]>
            </summary>
                                    <updated>2023-02-27T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Subscribe using Bancontact and Ideal]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/subscribe-using-bancontact-and-ideal" />
            <id>https://flareapp.io/subscribe-using-bancontact-and-ideal</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>You might think every country has the same popular payment method, but that's not the case. In some countries, credit cards are pretty standard. In others, most people don't even have a credit card.</p>
<p>European customers that don't have a credit card could already create a Flare subscription using SEPA. You only need a valid IBAN number, and you're good to go! But even SEPA might feel strange in countries where it's not common.</p>
<p>In Belgium, one of the most known payment methods is Bancontact. Ideal is the most popular method in the Netherlands. Starting today, we accept payments via Bancontact and Ideal</p>
<p><img src="https://content.spatie.be/assets/flare/subscribe-using-bancontact-and-ideal/bancontact.jpg" alt="stripe-image" /></p>
<p>Adding this payment method was quickly achieved using the <a href="https://stripe.com/docs/customer-management/get-started">Stripe Billing Portal</a> we are using for handling payments</p>
<p>Since we were already receiving webhooks from Stripe within Flare, the only thing required to enable SEPA was flipping over this switch:</p>
<p><img src="https://content.spatie.be/assets/flare/subscribe-using-bancontact-and-ideal/bancontact-switch.jpg" alt="stripe-image" /></p>
<p>For our existing customers who want to change their payment method to Bancontact, Ideal or SEPA, Stripe doesn't support customers changing their payment method automatically. Don't hesitate to contact us via support; we'll help you switch.</p>
]]>
            </summary>
                                    <updated>2022-10-13T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[You might not need `useRef` for that]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/you-might-not-need-useref-for-that" />
            <id>https://flareapp.io/you-might-not-need-useref-for-that</id>
            <author>
                <name><![CDATA[Sam]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>According to the React maintainers, React developers <a href="https://beta.reactjs.org/learn/you-might-not-need-an-effect">reach for the </a><a href="https://beta.reactjs.org/learn/you-might-not-need-an-effect"><code>useEffect</code></a><a href="https://beta.reactjs.org/learn/you-might-not-need-an-effect"> hook too quickly</a>.  It is far from the only hook with many naive usages. Why won't we go through my favorite example of an incorrect usage for <code>useRef</code>?</p>
<h2>What is the <code>useRef</code> hook?</h2>
<p>In React, the data used for rendering is immutable. A change in a piece of state is committed through a setter or reducer.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> Component </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">name</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> setName</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> useState</span><span style="color: #81A1C1">&lt;string&gt;</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">input</span></span>
<span class="line"><span style="color: #D8DEE9FF">		  type</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">text</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		  value</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">name</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		  onChange</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">e </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setName</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">e</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">target</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">value</span><span style="color: #ECEFF4">)}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>These changes are observable to execute effects, invalidate memoization and rerender your component.</p>
<p><code>useRef</code> initializes and gives you access to a mutable variable across executions of your component or hook.<br />
Mutable because on each render, <code>useRef</code> will return the same <code>RefObject</code> to which you can store data.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> Component </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> buttonPresses </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> useRef</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">number</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">button onClick</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> buttonPresses</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">current</span><span style="color: #81A1C1">++</span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">/&gt;;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>They do not trigger all the cool stuff that the observable state does.</p>
<p>A powerful and straightforward way to look at React is that it wants us to <a href="https://twitter.com/RyanCarniato/status/1353801411331416070">keep our state and UI in sync</a>.</p>
<h2>Where and when can you use <code>useRef</code>?</h2>
<p><code>useRef</code> is not just an escape hatch but can be used to read and write data in event handlers and effects. Remember that changing the ref won't trigger the <code>useEffect</code> through its dependency array.</p>
<p>It's not unreasonable to think that you can only pass a <code>RefObject</code> to a jsx <code>ref</code> attribute.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> container </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> useRef</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">HTMLDivElement</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">div ref</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">container</span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">children</span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">div</span><span style="color: #81A1C1">&gt;;</span></span>
<span class="line"></span></code></pre>
<p>Note that we initialise <code>useRef</code> with <code>null</code>.  <code>container.current</code> is reset to <code>null</code> whenever the node unmounts. Therefore <code>container.current</code> will only ever be an <code>HTMLDivElement</code> or <code>null</code>. It is important for typesafety and generally considered good practice to always pass something to the <code>useRef</code> initializer.</p>
<p>You should use this pattern whenever you want to access your DOM nodes inside <code>useEffect</code> or event handlers.</p>
<p>For example, when we want a click on the <code>::backdrop</code> of a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"><code>&lt;dialog&gt;</code></a> to close it.<br />
In the <code>onClick</code> event handler, we first check if the click target is the dialog element.<br />
Then we can use the native <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/close"><code>dialog.close()</code></a> API to close it.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> Dialog </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">({</span><span style="color: #D8DEE9FF"> children</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> state </span><span style="color: #ECEFF4">})</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> dialogRef </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> useRef</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">HTMLDialogElement</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> handleClick </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">e</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">e</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">target </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> dialogRef</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">current</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> dialogRef</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">current</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">close</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">dialog ref</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">dialogRef</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> onClick</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">handleClick</span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">children</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">dialog</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<h2>Where it breaks down</h2>
<p>When I explained you could use mutable refs inside of <code>useEffect</code>, and you can assign a DOM node to that mutable ref through the ref attribute, you might try to add and remove event listeners inside of <code>useEffect</code>.</p>
<p>This pattern was deployed inside <a href="https://auto-animate.formkit.com">formkit's auto-animate</a>. I discovered this when I checked why their react hook was only sometimes working for me.</p>
<p>This is a slightly simplified version of the <a href="https://github.com/formkit/auto-animate/blob/master/src/react/index.ts">bundled hook</a>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">import </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> useEffect</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> useRef</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> RefObject </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> from </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">react</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"><span style="color: #D8DEE9FF">import autoAnimate</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> AutoAnimateOptions </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> from </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">../index</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">* AutoAnimate hook for adding dead-simple transitions and animations to react.</span></span>
<span class="line"><span style="color: #616E88">* </span><span style="color: #81A1C1">@param</span><span style="color: #616E88"> </span><span style="color: #8FBCBB">options</span><span style="color: #616E88"> - Auto animate options</span></span>
<span class="line"><span style="color: #616E88">* @returns</span></span>
<span class="line"><span style="color: #616E88">*/</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">export </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> useAutoAnimate</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T extends Element</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	options: Partial</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">AutoAnimateOptions</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">RefObject</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> element </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> useRef</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #88C0D0">useEffect</span><span style="color: #ECEFF4">(()</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #88C0D0">		</span><span style="color: #81A1C1">if</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">element</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">current </span><span style="color: #81A1C1">instanceof</span><span style="color: #88C0D0"> </span><span style="color: #8FBCBB">HTMLElement</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #88C0D0">			autoAnimate</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">element</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">current</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> options</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #88C0D0">	</span><span style="color: #ECEFF4">},</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">[</span><span style="color: #88C0D0">element</span><span style="color: #ECEFF4">])</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">element</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>And this is how I consumed the <code>useAutoAnimate</code> hook.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">import </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> useAutoAnimate </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> from </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@formkit/auto-animate/react</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> Component </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">container</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> useAutoAnimate</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">HTMLUlElement</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">items</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> error</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">useSwr</span><span style="color: #ECEFF4">(</span><span style="color: #616E88">/* swr config */</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">section</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">{</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF">items </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF">error </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">spinner </span><span style="color: #81A1C1">/&gt;</span><span style="color: #ECEFF4">)}</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">items </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">ul ref</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">container</span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">items</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">map</span><span style="color: #ECEFF4">(</span><span style="color: #616E88">/* item renderer */</span><span style="color: #ECEFF4">)}</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">ul</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">)}</span><span style="color: #D8DEE9FF">	</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">section</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Did you spot the problem?<br />
This fragment has the unobservable reference <code>element</code> inside the dependency array of a <code>useEffect</code>, which does not work. This <code>useEffect</code> will only execute after the initial render. If the initial render has <code>items</code>, the ref will hold the <code>&lt;ul&gt;</code> DOM node.</p>
<p>Due to waiting for data to fetch, <code>element</code> hasn't received the DOM node and fails to register the autoAnimate.</p>
<h2>Callback refs to the rescue</h2>
<p>Let us figure out what the jsx <code>ref</code> attribute accepts as value.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">interface</span><span style="color: #D8DEE9FF"> RefObject&lt;T&gt; </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">	readonly current</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> T </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">type RefCallback</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">instance</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> T </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> void</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">type Ref</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> RefCallback</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> RefObject</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"></span></code></pre>
<p>From the React docs, we know that <code>RefCallback</code> receives the DOM node when being rendered and <code>null</code> on unmount. With this knowledge, we can rewrite the hook to accept DOM updates.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">import </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> useCallback </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> from </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">react</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">import autoAnimate</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> AutoAnimateOptions </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> from </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@formkit/auto-animate</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">type Options </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> Partial</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">AutoAnimateOptions</span><span style="color: #81A1C1">&gt;;</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> useAutoAnimate </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">T extends HTMLElement</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">options</span><span style="color: #81A1C1">?:</span><span style="color: #D8DEE9FF"> Options</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">((</span><span style="color: #D8DEE9FF">element</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> T </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> void</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">useCallback</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">  </span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">element</span><span style="color: #81A1C1">:</span><span style="color: #88C0D0"> T </span><span style="color: #81A1C1">|</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">{</span><span style="color: #88C0D0">  </span></span>
<span class="line"><span style="color: #88C0D0">            </span><span style="color: #81A1C1">if</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #88C0D0">element</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">return;</span><span style="color: #88C0D0">  </span></span>
<span class="line"><span style="color: #88C0D0">            autoAnimate</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">element</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> options</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span><span style="color: #88C0D0">  </span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #ECEFF4">},</span><span style="color: #88C0D0">  </span></span>
<span class="line"><span style="color: #88C0D0">        </span><span style="color: #ECEFF4">[</span><span style="color: #88C0D0">options</span><span style="color: #ECEFF4">]</span><span style="color: #88C0D0">  </span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"></span></code></pre>
<h2>An example with ResizeObserver</h2>
<p>I was building a new pagination interaction for Flare's redesign, and a <code>ResizeObserver</code> came into play. This example shows that <code>useRef</code> is useful in other ways than accessing the DOM and how registering and cleaning up DOM observers/listeners can be accomplished using a callback ref.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> Component </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">size</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> setSize</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> useState</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">ResizeObserverSize </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null&gt;</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> observer </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> useRef</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">ResizeObserver</span><span style="color: #81A1C1">&gt;</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ResizeObserver</span><span style="color: #ECEFF4">((</span><span style="color: #D8DEE9FF">entries</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #88C0D0">setSize</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">entries</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">borderBoxSize</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> registerResizeObserver </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">useCallback</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">instance </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">{</span><span style="color: #88C0D0">  </span></span>
<span class="line"><span style="color: #88C0D0">	    </span><span style="color: #81A1C1">if</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">instance</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">return</span><span style="color: #88C0D0"> observer</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">current</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">observe</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">instance</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span><span style="color: #88C0D0">  </span></span>
<span class="line"><span style="color: #88C0D0">	    </span></span>
<span class="line"><span style="color: #88C0D0">	    observer</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">current</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">disconnect</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span><span style="color: #88C0D0">   </span></span>
<span class="line"><span style="color: #88C0D0">	</span><span style="color: #ECEFF4">},</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">[])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">ul ref</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">registerResizeObserver</span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">			</span><span style="color: #81A1C1">...</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">ul</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Note that by defining the <code>ResizeObserver</code>'s callback inside of <code>Component</code> it can access state setters but cannot access the updated state because it created a closure on initialisation.</p>
<h2>Read more</h2>
<p>The React team is hard at work on building better documentation so that we can focus on writing better React code.<br />
<a href="https://beta.reactjs.org/learn/referencing-values-with-refs">referencing values with refs</a> goes into more detail on how to use <code>useRef</code> safely.<br />
The challenges at the bottom of <a href="https://beta.reactjs.org/learn/manipulating-the-dom-with-refs">manipulating the dom with refs</a> can build up your understanding of using DOM apis in react.</p>
<p>The old <a href="https://reactjs.org/docs/refs-and-the-dom.html#callback-refs">callback refs</a> documentation outlines the idea of using a function for more fine-grained control over when refs are set and unset. The caveats of which can be solved using the <code>useCallback</code> hook.</p>
]]>
            </summary>
                                    <updated>2022-09-02T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[You can now subscribe to Flare via SEPA Payments]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/you-can-now-subscribe-to-flare-via-sepa-payments" />
            <id>https://flareapp.io/you-can-now-subscribe-to-flare-via-sepa-payments</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>The SEPA direct debit payment system is a uniform payment system used by the European Union and a few other countries like the United Kingdom, Iceland, Norway and more. It allows recurring payments to be made without credit cards.</p>
<p>You can create a Flare subscription using SEPA starting today, totally omitting credit cards. You only need a valid IBAN number, and you're good to go!</p>
<p><img src="https://content.spatie.be/assets/flare/you-can-now-subscribe-to-flare-via-sepa-payments/stripe-sepa.png" alt="stripe-image" /></p>
<p>Adding this payment method was quickly achieved using the pre-built Stripe Billing Portal we migrated to a few weeks ago.</p>
<p>Since we were already receiving webhooks from Stripe within Flare, the only thing required to enable SEPA was flipping over this switch:</p>
<p><img src="https://content.spatie.be/assets/flare/you-can-now-subscribe-to-flare-via-sepa-payments/stripe-sepa-switch.png" alt="stripe-image" /></p>
<p>For our existing customers who want to change their payment method to SEPA,  at the moment, Stripe doesn't support customers changing their payment method from credit card to SEPA. Don't hesitate to contact us via support, and we'll help you switch.</p>
]]>
            </summary>
                                    <updated>2022-03-15T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[How we improved Flare in 2021]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-we-improved-flare-in-2021" />
            <id>https://flareapp.io/how-we-improved-flare-in-2021</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>The main goal of Flare is to be the best error tracking for Laravel applications. When we launched Flare, we offered a lot of niceties like decompiled Blade views, solutions suggestions, and much more.</p>
<p>Work on a SaaS application is never done. There are always things to improve. And because the Laravel ecosystem is constantly moving, we have to adapt Flare. Here's a rundown of all the things things we improved in Flare in the past year.</p>
<h2>Making Livewire a first-class citizen</h2>
<p><a href="https://laravel-livewire.com">Livewire</a> has gained a lot of popularity these past years. What was initially viewed as a fun experiment with Blade views is now a valid architecture for building production worthy applications.</p>
<p>In Flare, we now recognise exceptions coming from Livewire components. For those exceptions, we display a Livewire tab.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/livewire1.png" alt="screenshot" /></p>
<p>That tab displays the entire state of the Livewire component in which the exception occurred.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/livewire2.png" alt="screenshot" /></p>
<p>When you make a typo in calling a method on a Livewire component, we'll also suggest the correct method. Of course, this works for properties as well.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/livewire-method-solution.png" alt="screenshot" /></p>
<h2>Showing detailed information for Laravel jobs</h2>
<p>When an error occurs inside a job, we now show all properties of that job, and this will potentially help you fix the problem faster.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/jobs.png" alt="screenshot" /></p>
<p><a href="https://flareapp.io/blog/38-flares-new-job-tab-knows-all-about-your-failed-jobs">Read more in this blog post</a></p>
<h2>Vastly improved error search</h2>
<p>When Flare was launched, you had to search for errors in a basic  GitHub like search field that didn't have any autocompletion. That search now has autocompletion, which makes it much more powerful.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/search.gif" alt="screenshot" /></p>
<p>Another improvement that we made was showing a nice graph with the error counts of all the errors.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/search.gif" alt="screenshot" /></p>
<p><a href="https://flareapp.io/blog/36-building-a-better-search-with-monaco-and-amcharts">Read more in this blog post</a></p>
<h2>Integrating with GitHub Issues</h2>
<p>Errors on Flare can now be associated with issues on GitHub and vice versa. This integration allows to:</p>
<ul>
<li>
<p>create a GitHub issue directly on a Flare error</p>
</li>
<li>
<p>associate a GitHub issue with a Flare error by mentioning a Flare URL in the GitHub issue</p>
</li>
<li>
<p>automatically resolve an error on Flare when you close the GitHub issue</p>
</li>
<li>
<p>automatically close a GitHub issue when you resolve an error in Flare</p>
</li>
</ul>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/github-menu.png" alt="screenshot" /></p>
<p><a href="https://flareapp.io/blog/23-introducing-our-github-integration">Read more in this blog post</a></p>
<h2>Notifications via Discord, Microsoft Teams and Telegram</h2>
<p>A key feature of our service is that we'll notify you when errors occur. Each development team has their favourite way of getting notified. When enough of our users request a specific notification channel, we add it to Flare.</p>
<p>This year we added support for Discord, Microsoft Team and Telegram.</p>
<p>Here's how the notification looks like in Discord.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/discord.png" alt="screenshot" /></p>
<p>And here's how's it looks like when using Microsoft Teams.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/ms-teams.png" alt="screenshot" /></p>
<p>For Telegram, we went one step further and made the notifications interactive. This allows you to resolve or snooze a notification and even creating a GitHub issue right from Telegram.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/new-error.png" alt="screenshot" /></p>
<p>More info in these <a href="https://flareapp.io/blog/9-flare-can-now-notify-you-via-discord-and-microsoft-teams">two</a> <a href="https://flareapp.io/blog/37-introducing-our-new-telegram-integration">blogposts</a>.</p>
<h2>Interactive mail notifications</h2>
<p>We've made our mail notifications more powerful. You can resolve, snooze, or create a GitHub issue right from a mail.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/interactive-mail.png" alt="screenshot" /></p>
<p><a href="https://flareapp.io/blog/13-mail-notifications-now-allow-to-snooze-and-resolve-errors">Read more in this blog post</a></p>
<h2>Showing error trends and favourites at the project overview</h2>
<p>You can now mark a project as a favourite at the project overview. These projects will be displayed at the top of the list in a card showing extra details.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/project-favourite.png" alt="screenshot" /></p>
<p>For each project in the list, we also show a little trend graph that immediately shows if there are more errors coming in as usual.</p>
<p><a href="https://flareapp.io/blog/22-meet-the-new-projects-overview-with-error-trends-and-favourites">Read more in this blog post</a></p>
<h2>Sending a monthly error report mail</h2>
<p>At the end of each month, we now email you a summary of all projects and our latest feature announcements.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/monthly-mail.png" alt="screenshot" /></p>
<p><a href="https://flareapp.io/blog/14-introducing-the-monthly-error-report-mail">Read more in this blog post</a></p>
<h2>Handling customer feedback using a support bubble form</h2>
<p>We highly value the feedback and feature requests we get from our users, and that's why we want to make it as easy as possible to communicate with us.</p>
<p>When you log into Flare, you'll now see the support bubble on the bottom right of every page. Click the bubble to open the support form that can be used to submit a bug report or a suggestion. It is as simple as that.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/support-bubble.gif" alt="screenshot" /></p>
<p>Under the hood, this uses <a href="https://github.com/spatie/laravel-support-bubble">our laravel-support-bubble package</a>.</p>
<h2>Interacting with Flare via the API</h2>
<p>Behind the scenes, Flare always had an API for people that requested access. We've polished and <a href="https://flareapp.io/docs/general/using-the-api">documented</a> that API and made it possible for every user to use it.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/api-tokens.png" alt="screenshot" /></p>
<h2>Miscellaneous UI improvements</h2>
<p>You can now easily see if an error came from the web, CLI, or queue on an error card.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/type.png" alt="screenshot" /></p>
<p>We now show who resolved an error and via which channel.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/resolve-channel.png" alt="screenshot" /></p>
<p>On the request tab, you'll now see the curl command that you can use to retry the request.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/curl.jpg" alt="screenshot" /></p>
<p>On the debug tab, rendering of the query bindings has been improved.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/bindings.png" alt="screenshot" /></p>
<p><a href="https://flareapp.io/docs/flare/general/spike-protection">Spike protection</a> will prevent all events of your plan from being used in a small timespan. You can now see the periods when Spike protection was active.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/spike-protection.png" alt="screenshot" /></p>
<p>We've added a &quot;select all&quot; link to select all errors in a project. This way, you could snooze, resolve, or delete all project errors in one go.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/select-all.png" alt="screenshot" /></p>
<p>Our billing portal got a new coat of paint. Behind the scenes, we're <a href="https://flareapp.io/blog/29-migrating-our-billing-portal-to-the-latest-version-of-laravel-spark">using</a> the latest version of Laravel Spark.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/spark-next.png" alt="screenshot" /></p>
<p>It was long overdue, but we finally made our documentation searchable. Code highlighting of the code snippets looks better now that we render them <a href="https://flareapp.io/blog/33-better-code-block-highlighting-on-our-blog-and-docs">using Shiki</a>.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/search.png" alt="screenshot" /></p>
<h2>Removing inactive users and teams automatically</h2>
<p>Like many other SaaS applications, you can try out Flare using a trial period. Of course, not everybody that tries out Flare will convert, and after the trial period is over, some people will not use the service anymore.</p>
<h2>Snoozing notifications per application version</h2>
<p>We've introduced the concept of an application version in Flare. And you can now snooze errors for a specific version. This way, you will get notified if the same error occurs again in a new version of your app.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/snooze-version.png" alt="screenshot" /></p>
<p><a href="https://flareapp.io/blog/11-snooze-notifications-per-application-version">Read more in this post</a></p>
<h2>In closing</h2>
<p>We're very proud of the improvements we've made to Flare in the past 12 months. In 2022, we'll continue listening to the feedback from our users. We can already share that we will revamp our entire UI, and work on it is well underway. We can't share a screenshot of the new Flare UI yet, but it will take hints of the new layout we're implementing for Ignition.</p>
<p>Here's how the new version of Ignition will look like.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-improved-flare-in-2021/spatie-ignition.png" alt="screenshot" /></p>
<p>You can see a preview in <a href="https://twitter.com/flareappio/status/1460560582382428162">this Twitter thread</a>!</p>
<p>If you have any suggestions to make Flare better, let us know by <a href="mailto:support@flareapp.io">mailing us</a> or posting your suggestion in <a href="https://github.com/spatie/flareapp.io-roadmap/discussions">our roadmap repo on GitHub</a>. We're listening!</p>
]]>
            </summary>
                                    <updated>2021-12-15T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Better support for Livewire in Flare and Ignition]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/better-support-for-livewire-in-flare-and-ignition" />
            <id>https://flareapp.io/better-support-for-livewire-in-flare-and-ignition</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Livewire is quickly gaining traction in the Laravel ecosystem. It allows you to write dynamic web applications without ever even having to touch JavaScript! That's why we've decided to bring first-party support for Livewire components to Flare and Ignition.</p>
<p>Starting from Ignition v2.17 and above, if something goes wrong within your Livewire component's lifecycle, the accompanying error page includes a new tab that looks like this:</p>
<p><img src="https://content.spatie.be/assets/flare/better-support-for-livewire-in-flare-and-ignition/livewire.png" alt="Livewire screenshot" /></p>
<p>This new tab will show up on your local Ignition error page as well as in Flare's reports. It show which component was being rendered when the error occurred, what updates were triggered right before (property updated, method called or event send) and the data used to render the component.</p>
<p>We've also added a couple new solutions to Ignition specifically for Livewire. For example, a typo in a property name will trigger a solution that tries to suggest the corrent name:</p>
<p><img src="https://content.spatie.be/assets/flare/better-support-for-livewire-in-flare-and-ignition/livewire-property-solution.png" alt="Livewire property solution screenshot" /></p>
<p>The solution is displayed when a method with a typo is wrongfully called in a Livewire component:</p>
<p><img src="https://content.spatie.be/assets/flare/better-support-for-livewire-in-flare-and-ignition/livewire-method-solution.png" alt="Livewire method solution screenshot" /></p>
<p>Whether an exception is thrown in a Livewire component, Inertia response or a regular Blade view. Flare and Ignition will try to make it as smooth as possible to show you what happened!</p>
]]>
            </summary>
                                    <updated>2021-11-29T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Grouping SQL errors]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/grouping-sql-errors" />
            <id>https://flareapp.io/grouping-sql-errors</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Flare gets a lot of errors every day. To keep a good overview, we try to group them as best as possible because you don't want to end with thousands of errors in your Flare project when there's only one bug within your codebase.</p>
<p>For most errors, grouping is relatively easy. We look at the top frame within the call stack where the error occurred and keep track of the class, method and line where the error occurred. When Flare receives another error, we again look at this top frame and group it by these parameters.</p>
<p>This method works pretty well for most exceptions thrown within PHP. But there are cases where this approach isn't giving the best results.</p>
<h2>SQL Exceptions</h2>
<p>A Laravel application will throw a <code>QueryException</code> whenever something goes wrong with your database. When we would group by top frame, all database errors would be grouped into a single item. That's something we don't want since those SQL errors could be entirely different.</p>
<p>We could not use the top frame of the call stack. But go deeper in the stack until we find a frame within your application. But what then if an external package caused the exception? It could be that we never reach an application frame. Also, the deeper we go through the call stack, the more general the path of execution becomes. We could be grouping errors that have nothing to do with each other.</p>
<p>Flare uses an alternative solution. We do not group by top frame in cases of a <code>QueryException</code> but by exception class and message. Now, these two errors become two different items within Flare:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">SQLSTATE</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">42</span><span style="color: #D8DEE9FF">S22</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Column not found</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1054</span><span style="color: #D8DEE9FF"> Unknown column </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">titl</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> in </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">field list</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">SQL</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> SELECT </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> FROM </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> WHERE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">titl</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare groupings</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">SQLSTATE</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">42</span><span style="color: #D8DEE9FF">S22</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Column not found</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1054</span><span style="color: #D8DEE9FF"> Unknown column </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> in </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">field list</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">SQL</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> UPDATE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> SET </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> WHERE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<p>Great! But what happens to the grouping when we run this piece of code, and it fails?</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">publish</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Post</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">post</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">published</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">post</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">update</span><span style="color: #ECEFF4">([</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">published</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The answer isn't that clear. It depends since a post can have multiple id's and <code>published</code> can be <code>0</code> or <code>1</code>. The following two exceptions could be thrown when this piece of code fails:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">SQLSTATE</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">42</span><span style="color: #D8DEE9FF">S22</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Column not found</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1054</span><span style="color: #D8DEE9FF"> Unknown column </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> in </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">field list</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">SQL</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> UPDATE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> SET </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> WHERE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">SQLSTATE</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">42</span><span style="color: #D8DEE9FF">S22</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Column not found</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1054</span><span style="color: #D8DEE9FF"> Unknown column </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> in </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">field list</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">SQL</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> UPDATE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> SET </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> WHERE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<p>This will cause Flare to create two entries for errors that should be grouped since they represent the same bug. We want to avoid these scenarios at all costs!</p>
<p>Let's take a look at the two queries:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">UPDATE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> SET </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> WHERE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"></span></code></pre>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">UPDATE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> SET </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> WHERE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"></span></code></pre>
<p>When we would use prepared statements with bindings where we sent the values separate from the query to the database server then, those queries would look like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">UPDATE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> SET </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> WHERE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span></span>
<span class="line"></span></code></pre>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">UPDATE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> SET </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> WHERE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span></span>
<span class="line"></span></code></pre>
<p>Great! The two queries are identical. We can now group using the exception class: <code>QueryException</code> and this query with bindings. Sadly enough, often prepared statements are entirely ignored, so we cannot use them.</p>
<p>But what if we could strip all values from these queries to look like queries with bindings?</p>
<h2>Parsing queries</h2>
<p>SQL queries are complicated, we've seen some simple queries earlier, but they can become quite complex. That's why we'll use a <a href="https://github.com/greenlion/PHP-SQL-Parser">SQL parser</a> to parse the queries into tokens so we know what should be replaced and whatnot.</p>
<p>The first thing we need to do is parse the query:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">PHPSQLParser</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">parsed</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Such a parsed query looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">UPDATE</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">10</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expr_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">table</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">table</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">`posts`</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">no_quotes</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">delim</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">parts</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">alias</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">hints</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">join_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">JOIN</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">ref_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">ref_clause</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">base_expr</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">`posts`</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sub_tree</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">SET</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expr_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expression</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">base_expr</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">`publishe` = 0</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sub_tree</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expr_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">colref</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">base_expr</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">`publishe`</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">no_quotes</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">delim</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">parts</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">              </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sub_tree</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expr_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">operator</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">base_expr</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">=</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sub_tree</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expr_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">const</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">base_expr</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">0</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sub_tree</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">WHERE</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">4</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expr_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">colref</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">base_expr</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">`id`</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">no_quotes</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">delim</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">parts</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">          </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sub_tree</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expr_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">operator</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">base_expr</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">=</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sub_tree</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #B48EAD">2</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expr_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">const</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">base_expr</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">1</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sub_tree</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>We only want to replace the constants within the query, so we update each token where the <code>expr_type</code> is <code>const</code> to not use a value like <code>1</code> or <code>0</code> but to use a <code>?</code> just like with the bindings in prepared statements:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expr_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">const</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">base_expr</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">1</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sub_tree</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>Becomes:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">array:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">expr_type</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">const</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">base_expr</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">?</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">sub_tree</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>The cool thing with this parser package is that we can convert these tokens back to a SQL query:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">PHPSQLCreator</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">parsed</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>And we're done! Our query now looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">UPDATE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> SET </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> WHERE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span></span>
<span class="line"></span></code></pre>
<p>Within Flare, the algorithm of stripping the token tree is a bit more complex than we show here, but the gist is the same. Take some raw values and replace them with question marks.</p>
<h2>More exotic queries</h2>
<p>But this is not where we end, although the parser is quite good at parsing the wildest queries. Sometimes it just cannot understand what's happening. For example:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">UPDATE users SET uuid </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">89637150</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">f807</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">11</span><span style="color: #D8DEE9FF">ea</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">aee4</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">51</span><span style="color: #D8DEE9FF">fe58f2b2b3</span></span>
<span class="line"></span></code></pre>
<p>In some database configurations, this query is valid. And our parser will only strip the first part of the UUID because it thinks the <code>-</code> signs are operators, and the other parts of the UUID are references to columns within your table:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">UPDATE users SET uuid </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> f807 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">11</span><span style="color: #D8DEE9FF">ea </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> aee4 </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">51</span><span style="color: #D8DEE9FF">fe58f2b2b3</span></span>
<span class="line"></span></code></pre>
<p>The same thing happens with dates:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">UPDATE users SET date </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">2020</span><span style="color: #D8DEE9FF">T17</span><span style="color: #81A1C1">:</span><span style="color: #B48EAD">24</span><span style="color: #81A1C1">:</span><span style="color: #B48EAD">34</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">012345</span><span style="color: #D8DEE9FF">Z</span></span>
<span class="line"></span></code></pre>
<p>That's why, before we parse the query, we preprocess it by stripping out some well-known types of strings like dates, emails, UUIDs:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #88C0D0">preg_replace</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #88C0D0">	</span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #88C0D0">		</span><span style="color: #ECEFF4">&#39;/</span><span style="color: #EBCB8B">\S</span><span style="color: #81A1C1">+</span><span style="color: #EBCB8B">@\S</span><span style="color: #81A1C1">+</span><span style="color: #EBCB8B">\.\S</span><span style="color: #81A1C1">+</span><span style="color: #ECEFF4">/&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #616E88">// emails</span></span>
<span class="line"><span style="color: #88C0D0">		</span><span style="color: #ECEFF4">&#39;/</span><span style="color: #EBCB8B">\d</span><span style="color: #ECEFF4">{</span><span style="color: #EBCB8B">2,4</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">-\d</span><span style="color: #ECEFF4">{</span><span style="color: #EBCB8B">2</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">-\d</span><span style="color: #ECEFF4">{</span><span style="color: #EBCB8B">2,4</span><span style="color: #ECEFF4">}[</span><span style="color: #EBCB8B"> T</span><span style="color: #ECEFF4">]</span><span style="color: #EBCB8B">?(\d</span><span style="color: #ECEFF4">{</span><span style="color: #EBCB8B">2</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">:\d</span><span style="color: #ECEFF4">{</span><span style="color: #EBCB8B">2</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">:\d</span><span style="color: #ECEFF4">{</span><span style="color: #EBCB8B">2</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">(</span><span style="color: #ECEFF4">[</span><span style="color: #EBCB8B">+-</span><span style="color: #ECEFF4">]</span><span style="color: #EBCB8B">\d</span><span style="color: #ECEFF4">{</span><span style="color: #EBCB8B">2</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">:\d</span><span style="color: #ECEFF4">{</span><span style="color: #EBCB8B">2</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">)?(\.\d</span><span style="color: #ECEFF4">{</span><span style="color: #EBCB8B">6</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">Z)?)?</span><span style="color: #ECEFF4">/&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #616E88">// dates</span></span>
<span class="line"><span style="color: #88C0D0">		</span><span style="color: #ECEFF4">&#39;/[</span><span style="color: #EBCB8B">0-9a-f</span><span style="color: #ECEFF4">]{</span><span style="color: #EBCB8B">8</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">\b-</span><span style="color: #ECEFF4">[</span><span style="color: #EBCB8B">0-9a-f</span><span style="color: #ECEFF4">]{</span><span style="color: #EBCB8B">4</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">-</span><span style="color: #ECEFF4">[</span><span style="color: #EBCB8B">0-9a-f</span><span style="color: #ECEFF4">]{</span><span style="color: #EBCB8B">4</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">-</span><span style="color: #ECEFF4">[</span><span style="color: #EBCB8B">0-9a-f</span><span style="color: #ECEFF4">]{</span><span style="color: #EBCB8B">4</span><span style="color: #ECEFF4">}</span><span style="color: #EBCB8B">-\b</span><span style="color: #ECEFF4">[</span><span style="color: #EBCB8B">0-9a-f</span><span style="color: #ECEFF4">]{</span><span style="color: #EBCB8B">12</span><span style="color: #ECEFF4">}/&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #616E88">// uuids</span></span>
<span class="line"><span style="color: #88C0D0">	</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #88C0D0">	</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">?</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">?</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">?</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #88C0D0">	</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">query</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Preprocessing makes the queries a bit more readable for our parser, which improves our final result drastically!</p>
<h2>Connection issues</h2>
<p>A <code>QueryException</code> always looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">SQLSTATE</span><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">42</span><span style="color: #D8DEE9FF">S22</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Column not found</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1054</span><span style="color: #D8DEE9FF"> Unknown column </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> in </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">field list</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">SQL</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> UPDATE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">posts</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> SET </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">publishe</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> WHERE </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">id</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span></code></pre>
<p>Did you notice the <code>SQLSTATE[42S22]</code> part? It is a <a href="https://en.wikipedia.org/wiki/SQLSTATE">code</a> telling you what went wrong with your database. For example, a connection issue in a MySql database has the following code: [HY000][2002] looks familiar?</p>
<p>Within Flare, we'll also take a look at these codes. When we notice the error is caused by connection issues and not a bug within your code, then we group all these errors since your database server was probably just a few moments down.</p>
<h2>Impossible queries</h2>
<p>Let's take a look at a final example:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">SELECT </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> FROM invoices WHERE number </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">123</span><span style="color: #D8DEE9FF">ABC</span></span>
<span class="line"></span></code></pre>
<p>Such a query is a difficult one. The parser doesn't know if <code>123ABC</code> is a constant or a reference to a column within your database table.</p>
<p>In the latter case, we don't want to replace the column name with a <code>?</code> since these are not values that change between exceptions.</p>
<p>In such cases, we do not strip the value and keep that part of the query.</p>
]]>
            </summary>
                                    <updated>2021-11-21T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Flare's new "job" tab knows all about your failed jobs]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/flares-new-job-tab-knows-all-about-your-failed-jobs" />
            <id>https://flareapp.io/flares-new-job-tab-knows-all-about-your-failed-jobs</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Today we've deployed our latest set of features! This includes a new &quot;job&quot; tab with a ton of extra context for failed jobs:</p>
<p><img src="https://content.spatie.be/assets/flare/flares-new-job-tab-knows-all-about-your-failed-jobs/jobs.png" alt="job context screenshot" /></p>
<p>Ignition's job tab will display information about the failed job's class, its properties, connection, queue, tags, chained jobs and a lot more. This is all powered by Ignition version 2.16.0, which was released earlier today.</p>
<h2>How does this work?</h2>
<p>Under the hood, Ignition registers a couple &quot;recorders&quot; that hook into your Laravel application. For example, the <code>DumpRecorder</code> will capture and send <code>dump</code> statements to Flare as extra context when an exception takes place. There's also the <code>LogRecorder</code> and <code>QueryRecorder</code> that will... you guessed it: capture and send logs and queries to Flare.</p>
<p>So starting today, Ignition version 2.15.0 and above also includes a <code>JobRecorder</code>. The <code>JobRecorder</code> registers itself in your application and waits for a <code>JobExceptionOccurred</code> event:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">events</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">listen</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">JobExceptionOccurred</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$this</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">record</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Laravel sends out a <code>JobExceptionOccurred</code> event each time an exception is thrown within a job. Once the event is captured, we can find the failed queue <code>Job</code> in its <code>$event-&gt;job</code> property. The <code>Job</code> object also contains some basic information about the queue and connection that it failed on:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">job</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">resolveName</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">connection</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">job</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getConnectionName</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">queue</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">job</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getQueue</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>Other properties like the job's id, when it was pushed onto the queue, the tags associated with it and its actual parameters are all contained in the serialized payload.</p>
<p>Since these jobs need to be executed in a different worker process (e.g. on Laravel Horizon or using Amazon SQS), they've been serialized like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">O:</span><span style="color: #B48EAD">21</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">\</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">App</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">Jobs</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">TestFlareJob</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">:11:{s:27:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">\u0000App</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">Jobs</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">TestFlareJob\u0000name</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;s:1:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">a</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;s:3:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">job</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:10:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">connection</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:5:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">queue</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:15:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chainConnection</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:10:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chainQueue</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:19:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chainCatchCallbacks</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:5:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">delay</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:11:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">afterCommit</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:10:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">middleware</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;a:0:{}s:7:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chained</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;a:2:{i:0;s:276:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">O:21:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">App</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">Jobs</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">TestFlareJob</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">:11:{s:27:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">\u0000App</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">Jobs</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">TestFlareJob\u0000name</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;s:1:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">b</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;s:3:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">job</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:10:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">connection</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:5:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">queue</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:15:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chainConnection</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:10:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chainQueue</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:19:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chainCatchCallbacks</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:5:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">delay</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:11:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">afterCommit</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:10:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">middleware</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;a:0:{}s:7:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chained</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;a:0:{}}</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;i:1;s:276:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">O:21:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">App</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">Jobs</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">TestFlareJob</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">:11:{s:27:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">\u0000App</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">Jobs</span><span style="color: #EBCB8B">\\</span><span style="color: #A3BE8C">TestFlareJob\u0000name</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;s:1:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">c</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;s:3:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">job</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:10:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">connection</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:5:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">queue</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:15:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chainConnection</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:10:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chainQueue</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:19:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chainCatchCallbacks</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:5:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">delay</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:11:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">afterCommit</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;N;s:10:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">middleware</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;a:0:{}s:7:</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">chained</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;a:0:{}}</span><span style="color: #EBCB8B">\&quot;</span><span style="color: #A3BE8C">;}}</span></span>
<span class="line"></span></code></pre>
<p>The get to the original data we need to deserialize this string. This will yield the original job object. We can then use class reflection to read its properties to an associative array:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">properties</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">collect</span><span style="color: #ECEFF4">((</span><span style="color: #81A1C1">new</span><span style="color: #88C0D0"> </span><span style="color: #8FBCBB">ReflectionClass</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">command</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getProperties</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">mapWithKeys</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ReflectionProperty</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">property</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">command</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">property</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">setAccessible</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">property</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">name</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">property</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getValue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">command</span><span style="color: #ECEFF4">)]</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>That's it! This <code>$properties</code> array will be added as context to the Flare report and will soon show up in your Flare reports!</p>
]]>
            </summary>
                                    <updated>2021-11-01T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Building a better search with Monaco and amCharts]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/building-a-better-search-with-monaco-and-amcharts" />
            <id>https://flareapp.io/building-a-better-search-with-monaco-and-amcharts</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>A couple of months ago we refactored a considerable part of Flare's search to better utilise ElasticSearch's amazing search capabilities. We introduced better filters with autocompletion and fuzzy matching. Using our <a href="https://github.com/spatie/elasticsearch-search-string-parser">elasticsearch-search-string-parser</a> package we also created a custom search language to query ElasticSearch.</p>
<p>Inspired by GitHub's issue and PR search, we decided to keep the UI as simple as possible: a plain text field with a simple dropdown for basic autocompletion. You can read all about <a href="https://flareapp.io/blog/28-refactoring-our-search-capabilities-using-elasticsearch-and-two-new-spatie-packages">this blog post</a>.</p>
<p>Even though we were pretty happy with the results, we felt that we could do better. Let's <code>git checkout search-v2</code> and see what's going on.</p>
<h2>A better date range selector</h2>
<p>For a start, using a plaintext query to specify a timeframe is not very fun. Queries like <code>is:unresolved received_at:&gt;2021-08-01 received_at:&lt;=2021-08-31</code> are a pain to read –let alone write– especially without syntax highlighting.</p>
<p>We quickly decided to replace the <code>received_at</code> filters with a fancy UI component we had been eyeing for a while: <a href="https://www.amcharts.com/demos/line-chart-with-scroll-and-zoom/">amCharts' zoomable/scrollable charts</a> and their <a href="https://www.amcharts.com/docs/v4/tutorials/plugin-range-selector/">range selector plugin</a>.</p>
<p><img src="https://content.spatie.be/assets/flare/building-a-better-search-with-monaco-and-amcharts/graph.gif" alt="Time range selector in action" /></p>
<p>This doesn't only solve the time range selector problem, but also provides a neat graph with the number of occurrences for each error matched by the search query. It also comes with some unique features like being able to handle 43.000 data points (one per minute for 30 days) and automatic grouping per day, hour or 10-minute interval, based on the zoom-level.</p>
<p>Resolving the chart data points was a bit tricky. We really wanted to filter the charts data using the ElasticSearch query. However, the occurrence counts and timestamps are <strong>only</strong> stored in the MySQL database. As a solution we ended up querying ElasticSearch for only the error IDs and nothing else. This kept the query short and the response data small. We then query MySQL for the actual occurrence counts per minute for only those error IDs.</p>
<p>Depending on the complexity of the search query, this whole process takes somewhere around 450ms. Because each query is pretty unique (different time frames, different search query, errors coming in all the time) we're not attempting to cache the graph data at all. In theory we could hash all these variables together and cache the graph data, but the cache hit percentage would be too low to make it worth it. That doesn't mean we can't manually cache some of the most frequent queries. For example, the occurrence graph data for the default search query (<code>is:unresolved is:unsnoozed</code>) is fetched entirely from cache without every hitting the ElasticSearch server.</p>
<h2>A better search field</h2>
<p><img src="https://content.spatie.be/assets/flare/building-a-better-search-with-monaco-and-amcharts/search.gif" alt="Time range selector in action" /></p>
<p>At this point we had a cool looking occurrence graph next to a very plain search field. Inspired by <a href="https://sourcegraph.com/search">Sourcegraph's search</a> and <a href="https://github.com/nhabuiduc/react-filter-box">this cool React package</a> we decided to try to compress the entire <a href="https://github.com/microsoft/monaco-editor">Monaco editor</a> (the open-source editor that powers VS Code) down to a single line and use it as an input field. As the Monaco editor is super extensible we can easily provide context-aware autocompletion, syntax highlighting and other goodies. Let's dive into the details.</p>
<h3>Installing Monaco</h3>
<p>The Monaco editor is no lightweight package. It comes in at 3MB (ungzipped) and includes a Webpack loader plugin to help shave off some of those unnecessary kilobytes. However, to avoid having to deal with Webpack and it's infinite complaints about our bundle, we decided to go with a <a href="https://github.com/suren-atoyan/monaco-react">suren-atoyan/monaco-react</a>. As the name suggests, this is a React wrapper around the Monaco editor. More importantly: it includes its own <code>monaco-loader</code> that works out of the box and without having to deal with Webpack.</p>
<h3>Monaco on a single line</h3>
<p>We can't simply set the editor's height to <code>1em</code> to turn it into a single line input field. There are a couple gotchas that need to be taken care of as well.</p>
<p>For starters, pressing the enter key in most code editors will insert a newline. It's really hard to get rid of that behaviour in Monaco without hacking into its internal event listeners and keybindinds (believe me, we tried). The easy way around is to hook into the editor's <code>onChange</code> event and literally replace every newline character with an empty string: <code>value.replace(/[\n\r]/g, '')</code>.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">Editor</span></span>
<span class="line"><span style="color: #D8DEE9FF">    height</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">16px</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    value</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">value</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    onChange</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">value </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setValue</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">value</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">replace</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">/</span><span style="color: #ECEFF4">[</span><span style="color: #88C0D0">\n\r</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">/</span><span style="color: #88C0D0">g</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #ECEFF4">))}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    options</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">options</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"></span></code></pre>
<p>While this is a somewhat unconventional solution, it still works with no performance impact or fanthom newlines.</p>
<p>Apart from setting the editor height to <code>16px</code> (= single line-height) and removing any newlines, we're passing in some <code>options</code> too. This options object contains a lot of the same keys you might recognise from VS Code's settings. For example, we had to disable line numbers using <code>lineNumbers: 'off'</code>. We also hid the default minimap, disabled all rulers, unregistered all keybinds and disabled the context menu. The devil really is in the details. After all those changes, our <code>&lt;SingleLineMonacoEditor /&gt;</code> component finally rendered a blank 16px high rectangle that can be typed in. Perfect!</p>
<h3>Adding a custom language for search</h3>
<p>To be able to highlight custom syntax, you need to be able to parse the custom syntax. Or so we thought. In practice it's really a lot easier. The Monaco editor only expects a <code>tokensProvider</code> that can convert a document into an array of tokens. Even easier: it comes with a token provider called <code>Monarch</code> out of the box and it honestly feels like cheating. Monarch is configured using <em>only</em> JSON and <a href="https://microsoft.github.io/monaco-editor/monarch.html">the Monarch config for highlighting all JavaScript code is only 150 lines (switch to the JavaScript sample)</a>.</p>
<p>For Flare's query language, we don't even need most of Monarch's features. We'll only provide an array of <code>keywords</code> (each search filter is a keyword) and a rule to apply a <code>filter</code> tag to keyword tokens. Take a look at the literal 13 lines of code for Flare's tokeniser:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    keywords: filter</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">map</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">filter </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> filter</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">keyword</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    tokenizer: </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        root: </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">\w</span><span style="color: #81A1C1">+/</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> cases</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">	                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@keywords</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">directive</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">	                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@default</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">identifier</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">				</span><span style="color: #ECEFF4">}},</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>In the tokeniser above, we're once again taking the easy route. The <code>\w+</code> regex will first match every &quot;word&quot; in the code. The tokeniser will then look at each of those words and determine if it's one of the <code>@keywords</code> we specified earlier. If it is, we're applying the <code>directive</code> type to the token. If it's not, we'll fall back to the <code>@default</code> case and apply the <code>identifier</code> type.</p>
<p>Finally, VS Code or Monaco are able to style these keywords using the configured a color scheme. We slapped some Flare purple on the default VS Code color scheme, registered it and called it a day:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">monaco</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">editor</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">defineTheme</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare-light</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #88C0D0">    base: </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">vs</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">    inherit: </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #88C0D0">    rules: </span><span style="color: #ECEFF4">[{</span><span style="color: #88C0D0"> token</span><span style="color: #81A1C1">:</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">directive</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> foreground</span><span style="color: #81A1C1">:</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">#7900f5</span><span style="color: #ECEFF4">&#39;</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">}],</span></span>
<span class="line"><span style="color: #88C0D0">    colors: </span><span style="color: #ECEFF4">{</span><span style="color: #88C0D0"> foreground</span><span style="color: #81A1C1">:</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">#332f51</span><span style="color: #ECEFF4">&#39;</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<h3>Autocompletion and code suggestions</h3>
<p>Getting suggestions from an external API to show up at the right places in the editor was probably the hardest part of this entire setup. Once again we feared having to dive into the world of tokenisers and abstract syntax trees to provide suggestions based on the current token and its context. Luckily, Monaco doesn't really care about any of that stuff and just expects us to return a <code>Array&lt;string&gt;</code> with suggestions based on the cursor position. Here's a quick example that will suggest <code>foo</code> and <code>bar</code> anywhere in the editor:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">monaco</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">langagues</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">registerCompletionItemProvider</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare-query-language</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #88C0D0">    provideCompletionItems: </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">model</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> position</span><span style="color: #ECEFF4">)</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #88C0D0">	    suggestions: </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">foo</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">bar</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span></span>
<span class="line"><span style="color: #88C0D0">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">})</span></span>
<span class="line"></span></code></pre>
<p>For Flare specifically, we're only able to suggest either filter keywords (e.g. <code>class</code>, <code>version</code>, ...) or filter values. The filter keyword suggestions are all a hardcoded array but the filter values need to be queried from ElasticSearch using our back-end API. We'll use the following regex to determine whether our cursor is currently in a filter keyword or in a filter value:</p>
<p><code>/\b(?&lt;filter&gt;[^\s:]+):?(?&lt;value&gt;\S+)?/g </code></p>
<p>If you don't speak regex, this just means to match any <code>key:value</code> occurrence in the query. You can also see the <code>:</code> and <code>value</code> parts are optional. This is because we also want to match the start of a new filter statement when the user is still typing. We can than then loop over all matches and compare the regex <code>match.index</code> and <code>match.length</code> to the cursor's <code>position</code> to determine if we're in a filter keyword or a filter value and make suggestions accordingly.</p>
<p>As mentioned before: if we're autocompleting a filter keyword we can simply return the a hardcoded array of filter keywords. For suggesting filter values, we need to execute and asynchronous API call. Luckily, Monaco supports returning a <code>Promise&lt;CompletionList&gt;</code> from the <code>provideCompletions</code> method. In pseudo-code, all of that looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> filters </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">version</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">class</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">type</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">is</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">async </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">provideCompletions</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">code position</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4"> 	</span><span style="color: #616E88">// If we&#39;re autocompleting filter values, make an API request</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">isFilterValue</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">code</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> position</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> suggestions </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> await </span><span style="color: #88C0D0">fetchSuggestions</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> position</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            suggestions</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">  	</span><span style="color: #616E88">// We&#39;re autocompleting a new filter or random text, return all available filters</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        suggestions: filters</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Finally, using some black magic and a stroke of luck we also managed to add a debounce in there without upsetting React's delicate rendering cycle.</p>
<h3>Bonus round: tooltips on hover</h3>
<p>Most of Flare's filters are pretty self explanatory and we initially weren't planning to show tooltips. Despite that, we figured that we're already downloading 3MB of Monaco editor including tooltip support so we might as well use it. /s</p>
<p>Once again, Monaco makes it really easy to hook in a custom <code>hoverProvider</code>. Just like the suggestion provider, the hover provider is given the entire document and the current mouse position as if it were a cursor. This means hovering over the 5th character in the document will pass <code>5</code> for the position. It then expects us to return the contents of a tooltip, if we want to show one.</p>
<p>We can re-use the <code>filter:value</code> regex from the suggestion provider above to once again determine if we're hovering over a filter keyword or a value. We'll then provide tooltips for just the filter keywords:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">provideHover</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">code</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> position</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">	</span><span style="color: #616E88">// Use regex to extract the filter keyword or value the cursor is in:</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> currentWord </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getCurrentWord</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">code</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> position</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">word</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">type </span><span style="color: #81A1C1">!==</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">keyword</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        range: </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Monaco</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">Range</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> word</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">start</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> word</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">end</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        contents: </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> value</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">`</span><span style="color: #A3BE8C">**${word.value}**</span><span style="color: #ECEFF4">`</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> value</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> keywords</span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">word</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">value</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">?</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">description }</span></span>
<span class="line"><span style="color: #D8DEE9FF">        ]</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    }</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span><span style="color: #ECEFF4">,</span></span>
<span class="line"></span></code></pre>
<h3>Lazy loading the input field</h3>
<p>Apart from the single-line Monaco configuration and the custom language definition, we're using a couple other tweaks to make the experience as smooth as possible. One of these tweaks is the loading indicator – or rather, lack thereof.</p>
<p>The <a href="https://github.com/suren-atoyan/monaco-react">monaco-react</a> package already show a <code>loading</code> placeholder that'll show a <code>Loading...</code> label by default. Instead of that label, we can also pass in a React component like a fully working input field styled to look exactly like the editor:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">Editor</span></span>
<span class="line"><span style="color: #D8DEE9FF">    value</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">value</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    onChange</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">handeOnChange</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">		</span><span style="color: #616E88">// ...</span></span>
<span class="line"><span style="color: #D8DEE9FF">    loading</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">InputField value</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">value</span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> onChange</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">value </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handleOnChange</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">value</span><span style="color: #ECEFF4">)}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">/&gt;</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">/&gt;</span></span>
<span class="line"></span></code></pre>
<p>The <code>onChange</code> callback can even be hooked up to capture any changes to the input field <strong>before</strong> the Monaco editor even loads. Once loaded it can then transition seamlessly into the full-blown editor.</p>
<h2>So you spent 2 months installing a graph component and copying VS Code?</h2>
<p>Yes! And we're pretty proud of it too. In the coming weeks we'll also bring this occurrence graph and the new search field to the error occurrence detail page. We're also looking at what other data we can index to provide useful search filters. Feel free to start a free 10-day trial (no credit card required) to play around with the new search features and be sure to let us know your feedback and ideas for interesting filters!</p>
]]>
            </summary>
                                    <updated>2021-09-30T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing our new Telegram integration]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-our-new-telegram-integration" />
            <id>https://flareapp.io/introducing-our-new-telegram-integration</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When something goes wrong in your application, Flare can notify you via various channels: mail, SMS, Slack, webhooks, ...</p>
<p>A lot of teams nowadays use Telegram to communicate, and we understand why. It has a very clean UI, is very fast, has a well-polished mobile app, and has excellent notification options.</p>
<p>As of today, Flare can notify you via Telegram. In this blog post, you'll see that we didn't only provide basic support but also went all the way with notification actions.</p>
<h2>Connecting Flare to Telegram</h2>
<p>To start using Telegram, go to the notification settings of either your team, personal account or project. You will see a new Telegram option.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/telegram-option.png" alt="screenshot" /></p>
<p>Click on the slider to start using Telegram. This dialog will open up.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/fresh-settings.png" alt="screenshot" /></p>
<p>In the dialog above, there are clear instructions to get started. You should invite our bot into your Telegram channel and issue the start command with a token. Let's do that.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/connected-telegram.png" alt="screenshot" /></p>
<p>When heading back to Flare, you'll see that we detected that our bot was asked to start in your Telegram channel.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/connected-flare.png" alt="screenshot" /></p>
<p>You can now click the events you want Flare to report in your channel and save the preferences.</p>
<p>After you've saved the Telegram preferences, you see a button to test if everything is working correctly. When pressed, we'll send a test notification to Telegram. This is how that notification looks like</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/send-test.png" alt="screenshot" /></p>
<p>Let's now send an error to Flare. For this example, the <code>php artisan flare:test</code> command was executed in one of our projects.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/new-error.png" alt="screenshot" /></p>
<p>You can see that all details of this error are displayed nicely in Telegram. Using that &quot;View error&quot; button, you can quickly see the entire area in Flare.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/view-error.png" alt="screenshot" /></p>
<p>Let's now press that &quot;Resolve&quot; button to mark the error as resolved. At Flare, you can see which telegram user did mark that error as resolved.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/resolved-error.png" alt="screenshot" /></p>
<p>In Telegram, you'll see that the &quot;Resolve&quot; button was replaced by an &quot;Unresolve&quot; button to undo the action.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/press-resolve.png" alt="screenshot" /></p>
<p>Let's now press the &quot;Snooze&quot; button. You'll see that it gets replaced by the various snooze options.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/snooze-options.png" alt="screenshot" /></p>
<p>If you don't want to snooze, you can press that &quot;Do not snooze&quot; button.</p>
<p>Let's try out the last available button, &quot;Create GitHub Issue&quot;. This one will send you to GitHub, to the create issue form, with all of the error details filled in.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-new-telegram-integration/create-github-action.png" alt="screenshot" /></p>
<p>How nice is that?</p>
<h2>In closing</h2>
<p>We hope that you like this new Telegram integration. Our team is constantly improving Flare. On our blog, you can read various posts on why and how features are added. If you have any feature suggestions, let us know using the little contact bubble in the lower right corner of this page.</p>
]]>
            </summary>
                                    <updated>2021-09-29T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Linking to Flare errors on your error page]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/linking-to-flare-errors-on-your-error-page" />
            <id>https://flareapp.io/linking-to-flare-errors-on-your-error-page</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>We've launched a new feature that will make it easier for users of your app to report specific errors.</p>
<p>When an error occurs in a web request, Laravel will show this error page by default.</p>
<p><img src="https://content.spatie.be/assets/flare/linking-to-flare-errors-on-your-error-page/default.png" alt="screenshot" /></p>
<p>If a user sees this page and wants to report this error to you, the user usually only reports the URL and the time the error was seen.</p>
<p>To let your users pinpoint the exact error they saw, you can display the UUID of the error sent to Flare.</p>
<p>If you haven't already done so, you <a href="https://laravel.com/docs/8.x/errors#custom-http-error-pages">can publish Laravel's default error pages</a> with this command.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">php artisan vendor</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF">publish </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">tag</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">errors</span></span>
<span class="line"></span></code></pre>
<p>Typically, you would alter the <code>resources/views/errors/500.blade.php</code> to display the UUID and optionally a URL of the latest error sent to Flare.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">@</span><span style="color: #88C0D0">extends</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">errors::minimal</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">@</span><span style="color: #88C0D0">section</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">title</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> __</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Server Error</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">))</span></span>
<span class="line"><span style="color: #81A1C1">@</span><span style="color: #88C0D0">section</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">code</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">500</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #81A1C1">@</span><span style="color: #88C0D0">section</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">message</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    Server error</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">div</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">a href</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">{{ Flare::sentReports()-&gt;latestUrl() }}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">           </span><span style="color: #ECEFF4">{{</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">sentReports</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">latestUuid</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}}</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">a</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">div</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #81A1C1">@</span><span style="color: #D8DEE9FF">endsection</span></span>
<span class="line"></span></code></pre>
<p>This is how that would look like in the browser.</p>
<p><img src="https://content.spatie.be/assets/flare/linking-to-flare-errors-on-your-error-page/uuid.png" alt="screenshot" /></p>
<p>That link returned by <code>Flare::sentReports()-&gt;latestUrl()</code> isn't publicly accessible, the page is only visible to Flare users that have access to the project on Flare.</p>
<p>In certain cases, multiple errors can be reported to Flare in a single request. To get a hold of the UUIDs of all sent errors, you can call <code>Flare::sentReports()-&gt;uuids()</code>. You can get links to all sent errors with <code>Flare::sentReports()-&gt;urls()</code>.</p>
<p>We hope that you like this nice new feature.</p>
]]>
            </summary>
                                    <updated>2021-09-13T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Handling customer feedback using a support bubble form]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/handling-customer-feedback-using-a-support-bubble-form" />
            <id>https://flareapp.io/handling-customer-feedback-using-a-support-bubble-form</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Like most pieces of software, Flare contains bugs. We try to catch those via automated/manual testing, and static analysis. But sometimes, one of those pesky bugs makes it to production where it stays unnoticed, and waiting to make a jump on an unexpecting user.</p>
<p>When this happens, we want our users to have an easy route to report such a bug. Of course we hope mosts of customers send us a mail, describing the issue to us and some users do exactly this. But we feel that it's human nature that if you want to make someone do something, you have to make it as easy as possible for them.</p>
<p>That's why many websites display a small widget - often called a chat or support bubble - where users can quickly report a bug, ask a quick question if something is not clear or even make suggestions for new features.</p>
<p>Last week, we decided to add such a support bubble to Flare as well. As an added bonus, we decided to open-source it too. That way you can add it to your own Laravel projects as well. In this blog post I'd like to tell you all about it.</p>
<h2>Using Flare's support bubble</h2>
<p>When you log into Flare, you'll see the support bubble on the bottom right of every page. Click the bubble to open the support form that can be used to submit a bug report or a suggestion. It is as simple as that.</p>
<p><img src="https://content.spatie.be/assets/flare/handling-customer-feedback-using-a-support-bubble-form/support-bubble.gif" alt="Support bubble demo" /></p>
<p>When a form is submitted a mail is sent to our Freshdesk where one of our team members will pick it up.</p>
<h2>A package to display a support bubble</h2>
<p>Flare isn't the only place where our team at Spatie wants to use a support bubble. That's why we've extracted our solution to a reusable package called <a href="https://github.com/spatie/laravel-support-bubble">spatie/laravel-support-bubble</a>. You can make use of this package in your Laravel project too.</p>
<p>Using this package you can quickly add a chat bubble that opens a support form on any page. It comes with batteries included:</p>
<ul>
<li>
<p>TailwindCSS styling out of the box</p>
</li>
<li>
<p>Won't ask user information if there's a logged in user</p>
</li>
<li>
<p>Includes some meta data like URL and IP address</p>
</li>
<li>
<p>Easily extendable using custom views, language files and event listeners</p>
</li>
<li>
<p>Honeypot included and set-up to keep spammers at bay</p>
</li>
</ul>
<p>The package can be installed in only a couple of minutes and we've built in quite some customisation options! Want to display your bubble using another color that Flare's beautiful purple? Just change a config value. Want to change or translate some labels? Publish the localisation files and start making changes. Want to customise the looks of the form even further? You can publish the views and components and make any changes you like.</p>
<p>When a form is submitted, we'll fire an event called <code>Spatie\SupportBubble\Events\SupportBubbleSubmittedEvent</code> that contains all submitted form values, the user's information, some request context and the entire <code>Illuminate\Http\Request</code>. Out of the box, the package uses this event to send a mail notification to the mail address you specify in the config file.</p>
<p>Alternatively, you can listen for that event in your own project and react to it in any way you like. You could store the values in the database, totally ignore it (we don't recommend this) or send the values to a third party help desk service.</p>
<h2>In closing</h2>
<p>To know more about how to install the package, and customising it, head over to the readme of <a href="https://github.com/spatie/laravel-support-bubble">spatie/laravel-support-bubble</a> on GitHub.</p>
<p>In this stream on YouTube, Freek demonstrates how to use the package, and explains in great detail how it works under the hood.</p>
<p>If you see a bug on Flare, or want to make a feature request, fill in our support form. We're listening!</p>
]]>
            </summary>
                                    <updated>2021-09-02T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Better code block highlighting on our blog and docs]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/better-code-block-highlighting-on-our-blog-and-docs" />
            <id>https://flareapp.io/better-code-block-highlighting-on-our-blog-and-docs</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>If you've visited our blog or documentation in the last couple of weeks, you might've noticed things look a little different. We're now highlighting all code blocks using <a href="https://github.com/spatie/shiki-php">Shiki</a>, an open-source syntax highlighter written in JavaScript. Shiki makes code blocks look a lot better than the previous highlighting method using <a href="https://github.com/spatie/commonmark-highlighter">highlight.php</a>.</p>
<h2>Some examples ✨</h2>
<p>Let's start with a simple PHP example:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Facade</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">FlareClient</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Report</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">FlareMiddleware</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Report</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">report</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">next</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">context</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">report</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">allContext</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">session</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">report</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">userProvidedContext</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">	    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">next</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">report</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>JavaScript poses no challenge either:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">import </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> flare </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> from </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">@flareapp/flare-client</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">functionThatMightThrow</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">error</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    flare</span><span style="color: #81A1C1">.</span><span style="color: #88C0D0">report</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">error</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>HTML looks great too:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">html lang</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">en</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">head</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">title</span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF">Laravel error tracking – Flare</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">title</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">meta name</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">description</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> content</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Full-stack Laravel error tracking made specifically for your Laravel applications and JavaScript frontends.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">meta charset</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">utf-8</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">meta name</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">viewport</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> content</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">width=device-width, initial-scale=1</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">link rel</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">alternate</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> type</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">application/atom+xml</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> href</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">https://flareapp.io/feed</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> title</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">flareapp.io</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">link rel</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">apple-touch-icon</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> sizes</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">180x180</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> href</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">https://flareapp.io/apple-touch-icon.png</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">link rel</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">icon</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> type</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">image/png</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> sizes</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">32x32</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> href</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">https://flareapp.io/favicon-32x32.png</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">link rel</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">icon</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> type</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">image/png</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> sizes</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">16x16</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> href</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">https://flareapp.io/favicon-16x16.png</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">link rel</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">manifest</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> href</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">https://flareapp.io/site.webmanifest</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">meta name</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">msapplication-TileColor</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> content</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">#4f0f8f</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF">meta name</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">theme-color</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> content</span><span style="color: #81A1C1">=</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">#4f0f8f</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">head</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"><span style="color: #81A1C1">&lt;/</span><span style="color: #D8DEE9FF">html</span><span style="color: #81A1C1">&gt;</span></span>
<span class="line"></span></code></pre>
<p>CSS never was so pretty:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">modal</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">header </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    flex: none</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    display: grid</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    align</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">items</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> center</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    padding</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">left</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">5</span><span style="color: #D8DEE9FF">rem</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    padding</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">right</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">5</span><span style="color: #D8DEE9FF">rem</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    padding</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">top</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">rem</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    padding</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">bottom</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span><span style="color: #D8DEE9FF">rem</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    height: </span><span style="color: #81A1C1">var</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">--tab-</span><span style="color: #D8DEE9FF">bar</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">height</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    background</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">color</span><span style="color: #81A1C1">:var</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">gray</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">200</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">@</span><span style="color: #88C0D0">media </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">min</span><span style="color: #81A1C1">-</span><span style="color: #88C0D0">width</span><span style="color: #81A1C1">:</span><span style="color: #88C0D0"> </span><span style="color: #B48EAD">768</span><span style="color: #88C0D0">px</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">modal</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">header </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        padding</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">left</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF">rem</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        padding</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">right</span><span style="color: #81A1C1">:</span><span style="color: #B48EAD">3</span><span style="color: #D8DEE9FF">rem</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>And last but not least, we can show diffs now:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">invitations</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #81A1C1">+++</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">invitations</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">HasMany</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">hasMany</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Invitation</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<h2>Behind the scenes</h2>
<p>All of the above is powered by our newest <a href="https://spatie.be/docs/laravel-markdown/v1/introduction">laravel-markdown</a> package. It converts markdown to HTML and it's using Shiki and our <a href="https://github.com/spatie/shiki-php">shiki-php</a> package to highlight code blocks. Because Laravel has a built-in <code>MarkdownRenderer</code> that we like to use, we don't call <code>CommonMark</code> directly. Instead we resolve the renderer class from the container:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CommonMark</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">convertToHtml</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">+++</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">MarkdownRenderer</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toHtml</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Because Shiki is quite heavy on performance (a nodeJS server is started on the backend each time you're trying to highlight a code block) we decided to add an extra layer of caching:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">cache</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">remember</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">posts.</span><span style="color: #81A1C1">{$this-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">.formatted_text</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">MarkdownRenderer</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toHtml</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>On the <code>Post</code> model we added a listener that will (re-)cache the blogpost whenever it's saved. This will trigger when the model is created and updated:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">static::</span><span style="color: #88C0D0">saved</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Post</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">post</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #88C0D0">cache</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">delete</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">posts.</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">post</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">.formatted_text</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">post</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getFormattedTextAttribute</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// re-cache</span></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
]]>
            </summary>
                                    <updated>2021-08-23T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[How we're using static analysis to improve our codebase]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-were-using-static-analysis-to-improve-our-codebase" />
            <id>https://flareapp.io/how-were-using-static-analysis-to-improve-our-codebase</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>The initial Flare codebase was written almost two years ago. Though that's not so long ago, but PHP landscape changed drastically in that timeframe. PHP version 7.4 and 8.0 were significant releases, and static analyzers became a popular topic in the PHP world.</p>
<p>A static analyzer helps you to find bugs in your code without even running it. Popular static analyzers for PHP are <a href="https://psalm.dev">Psalm</a> and <a href="https://phpstan.org">PHPStan</a>. In this post, we're going to look at what such a static analyzer can find in a two-year-old codebase.</p>
<p>Since Flare is a Laravel project, we decided to use <a href="https://github.com/nunomaduro/larastan">Larastan</a>, a Laravel flavoured extension of PHPStan.</p>
<h2>Installing Larastan</h2>
<p>Larastan is installed just like any other PHP dependency with Composer:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">composer </span><span style="color: #81A1C1">require</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">--</span><span style="color: #D8DEE9FF">dev nunomaduro</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">larastan</span></span>
<span class="line"></span></code></pre>
<p>When Larastan is installed you create a <code>phpstan.neon</code> file in which you can configure PHPStan:</p>
<p>`includes:</p>
<ul>
<li>./vendor/nunomaduro/larastan/extension.neonparameters:<br />
paths:</li>
<li>applevel: 1ignoreErrors:</li>
<li>'#Unsafe usage of new static#'checkMissingIterableValueType: false<br />
`</li>
</ul>
<p>PHPStan has eight levels: one is the lowest level of error checking, and eight is the highest, most strict one. We're planning to raise the level in the coming months, so we know for sure that the Flare codebase is wholly checked.</p>
<p>Now you can run PHPStan like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">.</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">vendor</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">bin</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">phpstan analyse</span></span>
<span class="line"></span></code></pre>
<p>PHPStan will output a list of errors within your codebase like this one:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">------</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-----------------------------------------------------</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">  Line   Domain</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Subscription</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">Listeners</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">CreateInvoice</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">php      </span></span>
<span class="line"><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">------</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-----------------------------------------------------</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #B48EAD">17</span><span style="color: #D8DEE9FF">     Variable </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">invoice</span><span style="color: #D8DEE9FF"> in PHPDoc tag </span><span style="color: #81A1C1">@var</span><span style="color: #D8DEE9FF"> does not </span><span style="color: #81A1C1">match</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #D8DEE9FF">         assigned variable $cashierInvoice.                   </span></span>
<span class="line"><span style="color: #D8DEE9FF">  30     Access to an undefined property                      </span></span>
<span class="line"><span style="color: #D8DEE9FF">         Laravel\Cashier\Invoice::$id.                        </span></span>
<span class="line"><span style="color: #D8DEE9FF"> ------ ----------------------------------------------------- </span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF"> ------ ------------------------------------------------------ </span></span>
<span class="line"><span style="color: #D8DEE9FF">  Line   Domain/Subscription/Models/UsagePeriodDay.php         </span></span>
<span class="line"><span style="color: #D8DEE9FF"> ------ ------------------------------------------------------ </span></span>
<span class="line"><span style="color: #D8DEE9FF">  19     Call to an undefined method                           </span></span>
<span class="line"><span style="color: #D8DEE9FF">         App\Domain\Team\Models\Team::getApiUsageResetDate</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF">.  </span></span>
<span class="line"><span style="color: #D8DEE9FF"> ------ ------------------------------------------------------ </span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF"> ------ ------------------------------------------------------ </span></span>
<span class="line"><span style="color: #D8DEE9FF">  Line   Domain/Subscription/Notifications/ConfirmPayment.php  </span></span>
<span class="line"><span style="color: #D8DEE9FF"> ------ ------------------------------------------------------ </span></span>
<span class="line"><span style="color: #D8DEE9FF">  37     Access to an undefined property                       </span></span>
<span class="line"><span style="color: #D8DEE9FF">         Laravel\Cashier\Payment::$id.                         </span></span>
<span class="line"><span style="color: #D8DEE9FF"> ------ ------------------------------------------------------ </span></span>
<span class="line"></span></code></pre>
<h2>A look at some errors PHPStan found</h2>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">couponEligibleForBillable</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">coupon</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">---      </span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Billable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">billable</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">+++      </span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">billable</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">plan</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">options</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"></span></code></pre>
<p>This piece of code checks if a coupon can be used for a Team, <code>Team|Billable</code> is a valid union type since we're using PHP 8. But <code>Billable</code> is a trait that cannot be type hinted as a method's parameter.</p>
<hr />
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getTotalWithoutTax</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">float</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #81A1C1">+++</span><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">instanceof</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Refund</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #81A1C1">+++</span><span style="color: #D8DEE9FF">          </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">total</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">tax</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">+++</span><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">+++</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">amount_due</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">tax</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>We have an <code>Invoice</code> and <code>Refund</code> models, both using a trait with this code to get the total amount without tax. On the <code>Invoice</code> model, we use the <code>amount_due</code> property for this calculation. The <code>Refund</code> model has no such property, so we should use the <code>total</code> property.</p>
<hr />
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">didRecentlyUnsubscribe</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">subscribed</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">hasEverSubscribedTo</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">carbon</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">subscription</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">ends_at</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isBetween</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">(),</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">subDays</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">  </span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">carbon</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">trial_ends_at</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isBetween</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">(),</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">subDays</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Recently we've updated the Laravel Spark version within Flare. The method above was being used with the previous version of Spark. The new version of Spark doesn't require it anymore, which means it could be deleted entirely.</p>
<hr />
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">last_logged_in</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addMonths</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">6</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isPast</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">+++</span><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">last_logged_in_at</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addMonths</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">6</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isPast</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>This code in the <code>User</code> model checks if the user had logged in within the past six months. If not, it will mark the user to be deleted. The name <code>last_logged_in</code> was changed a few weeks ago to <code>last_logged_in_at</code> to follow the naming convention of other properties with a DateTime. We oversaw this one in our code. Luckily PHPStan found it!</p>
<hr />
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">---</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">invitations</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #81A1C1">+++</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">invitations</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">HasMany</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">         </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">hasMany</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Invitation</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Some of the relations within our <code>Teams</code> model were missing some typing. Luckily this was a real quick fix.</p>
<h2>Closing thoughts</h2>
<p>The errors above were only a tiny subset of fixes we did on the Flare codebase using PHPStan. In the coming months, we will pump up the PHPStan checking level and make our codebase as bug-free as possible.</p>
<p>You can find more information about Larastan and PHPStan in its <a href="https://github.com/nunomaduro/larastan">GitHub repository</a>. It's worth running it on your codebase, and you'll probably find bugs you looked over. Flare can catch bugs that PHPStan cannot find, so you can fix them later, isn't that nice?</p>
]]>
            </summary>
                                    <updated>2021-08-16T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[How we use ElasticSearch, Kibana and Filebeat to handle our logs]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-we-use-elasticsearch-kibana-and-filebeat-to-handle-our-logs" />
            <id>https://flareapp.io/how-we-use-elasticsearch-kibana-and-filebeat-to-handle-our-logs</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Flare runs on a few different servers and each one of them has its own purpose. We've got web servers that serve the Flare app and other public pages like this blog. Reporting servers will take dozens of error reports per second from our clients and store them for later processing. Finally, there are worker servers which will process these reports and run background tasks like sending notifications and so on.</p>
<p>Each one of these servers runs a Laravel installation that produce interesting metrics and logs. This is quite helpful when something goes wrong. The only problem is that, whenever something goes wrong, we need to manually log in to each server via SSH to check the logs.</p>
<p>In this blog post, we'll explain how we combine these logs in a single stream.</p>
<h2>Using ElasticSerach for logging</h2>
<p>There are a couple of services out there to which you can send all the logging output. They provide a UI for everything you send to them. We decided to not use these services because we already are using an ElasticSearch cluster to handle searching errors. It's rather straightforward use it too search our logging output too.</p>
<p>ElasticSearch provides an excellent web client called <a href="https://www.elastic.co/kibana/">Kibana</a>. This isn't only used to manage the ElasticSearch cluster and its contents. It can also show you logs that are sent to ElasticSearch as part of the ELK stack. It even has a real-time stream of logs.</p>
<p><img src="https://content.spatie.be/assets/flare/how-we-use-elasticsearch-kibana-and-filebeat-to-handle-our-logs/kibana.png" alt="https://flareapp.io/images/blog/kibana.png" /></p>
<h2>Improving performance using Filebeat</h2>
<p>When something is logged in our Flare API, we could immediately send that log message to ElasticSearch using the API. However, this synchronous API call would make the Flare API really slow. Every time something gets logged within Flare, we would need to send a separate request to our ElasticSearch cluster, which could happend hundreds of times per second.</p>
<p>Instead, we chose to use <a href="https://www.elastic.co/beats/filebeat">Filebeat</a>. It's a tool by ElasticSearch that runs on your servers and periodically sends log files to ElasticSearch. This happens in a separate process so it doesn't impact the Flare Laravel application. Using Filebeat, logs are getting send in bulk, and we don't have to sacrifice any resources in the Flare app, neat!</p>
<h2>Integration in Laravel</h2>
<p>Let's take a look at how this works. By default, the Laravel logging format looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span><span style="color: #B48EAD">2021</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">07</span><span style="color: #81A1C1">-</span><span style="color: #B48EAD">15</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">11</span><span style="color: #81A1C1">:</span><span style="color: #B48EAD">40</span><span style="color: #81A1C1">:</span><span style="color: #B48EAD">43</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> local</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">ERROR</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> something went wrong</span></span>
<span class="line"></span></code></pre>
<p>Filebeat (and ElasticSearch's ingress) need a more structured logging format like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">@timestamp</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">2021-07-15T11:40:43.000000+00:00</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">level</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">ERROR</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">logger</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">local</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">message</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">something went wrong</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>As Laravel uses the monolog package for logging, we only need to create a new formatter for Filebeat that will output logs like the JSON format shown above:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ElasticSearchFormatter</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">NormalizerFormatter</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">format</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">record</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">message</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">@timestamp</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">normalize</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">record</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">datetime</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">log</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">level</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">record</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">level_name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">logger</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">record</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">channel</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">isset</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">record</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">message</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">message</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">message</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">record</span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">message</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">toJson</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">message</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #EBCB8B">\n</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Now within the <code>logging.php</code> config file, we change the <code>daily</code> logger as such:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">daily</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">driver</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">daily</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">formatter</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">ElasticSearch</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ElasticSearchFormatter</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">path</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">storage_path</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">logs/laravel.log</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">level</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">env</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">LOG_LEVEL</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">debug</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">days</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">7</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">],</span></span>
<span class="line"></span></code></pre>
<p>We use the daily logger in Flare to ensure our server's hard drives are not getting overloaded with log files. This daily logger will only keep logs for the last seven days stored on the server.</p>
<p>Next, we need to <a href="https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-installation-configuration.html">install Filebeat</a> on all of our servers and configure it to send the context from the daily log files to the ElasticSearch cluster. The <code>filebeat.yml</code> config file to do so looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">filebeat:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  inputs:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> enabled</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">    json</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">add_error_key</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">    json</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">expand_keys</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">    json</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">keys_under_root</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">    json</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">overwrite_keys</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">    paths:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">PATH_TO_FLARE</span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">storage</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">logs</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">laravel</span><span style="color: #81A1C1">-*</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">log</span></span>
<span class="line"><span style="color: #D8DEE9FF">    type: log</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">output:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  elasticsearch:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    hosts:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">ELASTIC_SEARCH_IP</span><span style="color: #ECEFF4">}</span><span style="color: #81A1C1">:</span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF">ELASTIC_SEARCH_PORT</span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">logging:</span></span>
<span class="line"><span style="color: #D8DEE9FF">  files:</span></span>
<span class="line"><span style="color: #D8DEE9FF">    rotateeverybytes: </span><span style="color: #B48EAD">10485760</span></span>
<span class="line"></span></code></pre>
<p>Finally, the last thing left to do is configuring Kibana to read the Filebeat logs. This can be configured from the Kibana UI by going to the settings panel in <code>Oberserveability</code> -&gt; <code>Logs</code>. Check that the log indices contain the <code>filebeat-*</code> wildcard. The indices that match this wildcard will be parsed for logs by Kibana.</p>
<p>In the log columns configuration we also added the <code>log.level</code> and <code>agent.hostname</code> columns. This way we can see how severe a log entry was and what server it originated from.</p>
]]>
            </summary>
                                    <updated>2021-08-09T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Migrating our billing portal to the latest version of Laravel Spark]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/migrating-our-billing-portal-to-the-latest-version-of-laravel-spark" />
            <id>https://flareapp.io/migrating-our-billing-portal-to-the-latest-version-of-laravel-spark</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When Flare was launched, we used <a href="https://spark-classic.laravel.com">Laravel Spark classic</a> to take care of all user, team and billing functionality. We recently migrated to the <a href="https://spark.laravel.com">latest version of Laravel Spark</a>. It offers a beautiful billing management screen. Here's how it looks like.</p>
<p><img src="https://content.spatie.be/assets/flare/migrating-our-billing-portal-to-the-latest-version-of-laravel-spark/spark-next.png" alt="screenshot" /></p>
<p>Our new Spark-powered billing portal allows you to see all available plans easily, subscribe, download invoices, etc...</p>
<p>By migrating to the latest version of Spark, we could vastly simplify our codebase. In this blog post, we'd like to share how we performed this migration and how we customised Laravel Spark.</p>
<h2>How we used Laravel Spark classic</h2>
<p>When we started work on Flare, we decided very early on that we didn't want to code everything around team management and billing ourselves. Using Laravel Spark was a given since we already had some experience with it by building <a href="https://ohdear.app">Oh Dear</a>.</p>
<p>At Spatie, we very much <a href="https://sebastiandedeyne.com/why-i-prefer-react-over-vue/">prefer</a> working with React. We're also very detail focused when creating a UI. Because Spark classic was built with Vue components, that didn't work how we wanted, we made a drastic decision: we started using Spark in a headless way. We use everything that Spark classic offered on the backend but used nothing of the front end components. We created our own React components that hooked in the Spark classic back end.</p>
<p>For us, this worked out pretty well. Even though we had some work creating our own React billing components, Spark classic still saved us quite some time.</p>
<h2>Moving to the newest version of Spark</h2>
<p>Earlier this year, the Laravel team <a href="https://www.youtube.com/watch?v=-wAmFagQSzI">introduced</a> a new version of Spark. The significant difference between the classic and the new Spark is that the new Spark is a separate billing portal instead of a Laravel starter template project on steroids. The user and team management features have been removed; they now reside in a separate free package called <a href="https://jetstream.laravel.com/2.x/introduction.html">JetStream</a>.</p>
<p>We started migrating to the newest version of Spark for two reasons:</p>
<ol>
<li>
<p>The React components we built to handle billing were quite complex. We could remove this complexity from our app by moving to the new version of Spark while still providing a feature-rich billing portal.</p>
</li>
<li>
<p>The old Spark version still gets security updates, but no new features will be added. By moving to the latest version, we can take advantage of new additions that will be added to Spark in the future. In general, we like using the latest versions of everything.</p>
</li>
</ol>
<h2>Taking care of user and team management</h2>
<p>We relied on the user and team management of the Spark classic. The new Spark doesn't offer this functionality. We could try to migrate to JetStream, but we opted for a much simpler solution.</p>
<p>In the old Spark, you had to let <code>User</code> and <code>Team</code> model of your app extend the base <code>User</code> and <code>Team</code> classes of Spark.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Laravel</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Spark</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB; font-weight: bold">Team</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">		</span><span style="color: #616E88">// your code</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Before removing the old Spark dependency, we copied over all the code from <code>\Laravel\Spark\Team</code> to our own <code>App\Domain\Team\Models\Team</code> model (and did the same with the <code>User</code> model).</p>
<p>Apart from a few other minor changes, this refactor did work. Our custom users/team UI just kept functioning normally.</p>
<p>We did the same refactor with the other code that extended stuff from <code>\Laravel\Spark</code>: team invitations, API token generation, auth controllers.</p>
<p>Because we used old Spark in a headless way and did not rely on any of its UI, just pulling the back end to our own codebase was easy.</p>
<p>After everything was copied, we did a round of refactoring where we simplified the imported Spark code. Any Spark features that Spark offered that we didn't use were removed.</p>
<h2>Migrating the database</h2>
<p>By comparing the tables and stored values of the production version of Flare with the local version of Flare with new Spark installed in it, we could write migrations that modify the table structure towards what the latest version of spark expects. We've also written migrations to copy the old data before any tables or columns are dropped.</p>
<p>You'll find the migrations we've used in <a href="https://gist.github.com/freekmurze/020507c15ef7d0f8b75fe03f74e38fd4">this public gist on GitHub</a>. We tested this migration by copying over parts of production data to our local environment to verify if the migrations work as expected.</p>
<h2>Customising VAT handling</h2>
<p>For the most part, we're using the Spark billing portal as is. The only significant customisation we've done revolves around VAT handling.</p>
<p>To verify European VAT ids, Spark uses <a href="https://ec.europa.eu/taxation_customs/vies/vatRequest.html">the European VIES API</a>. We've noticed that this API is down a lot, resulting in people that cannot subscribe. Of course, this is not good for business.   It would be better to just accept the VAT number when the API is down and verify it later when the API is back up.</p>
<p>If you follow the rules strictly, you'll argue that, in theory, this isn't allowed as you are obliged to verify a VAT number before you invoice it. But in practice, we see that all people that sign up are using valid VAT numbers.</p>
<p>Luckily, it's easy to override how Spark handles VAT so we can implement this new behaviour. Spark handles VAT calculation in an action class which can be overridden in de container. In our <code>SparkServiceProvider</code>, we've added this binding.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// in app/Providers/SparkServiceProvider.php</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">singleton</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">   Spark</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Contracts</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CalculatesVatRate</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">   App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Subscription</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Spark</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CalculateVatRate</span><span style="color: #81A1C1">::class</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Inside of the <code>CalculateVatRate</code> we can our own custom <code>\App\Domain\Subscription\Support\VatCalculation\VatCalculator</code> class. Here's the code for that action.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Subscription</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Spark</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Subscription</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">VatCalculation</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">VatCalculator</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spark</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CalculateVatRate</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> SparkCalculateVatRate</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CalculateVatRate</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">SparkCalculateVatRate</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">calculate</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">homeCountry</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">postalCode</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatNumber</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">VatCalculator</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getTaxRateForLocation</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">postalCode</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatNumber</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Let's now take a look at the <code>App\Domain\Subscription\Support\VatCalculation\VATCalculator</code> itself. We're not going over all of this code in detail. The extra features that this code has over <a href="https://github.com/driesvints/vat-calculator">the default </a><a href="https://github.com/driesvints/vat-calculator"><code>VATCalculator</code></a><a href="https://github.com/driesvints/vat-calculator"> that ships with Spark</a> are:</p>
<ul>
<li>
<p>if the VIES API is down, we'll optimistically determine that the given VAT number is valid (we'll verify it later in a scheduled command)</p>
</li>
<li>
<p>responses from VIES will be cached</p>
</li>
<li>
<p>Greece, Norway and Turkey should not be charged VAT</p>
</li>
</ul>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Subscription</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">VatCalculation</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Carbon</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CarbonInterval</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Config</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Repository</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Cache</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Mpociot</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">VatCalculator</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Exceptions</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">VATCheckUnavailableException</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Mpociot</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">VatCalculator</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">VatCalculator</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> BaseVatCalculator</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">VatCalculator</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">BaseVatCalculator</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">baseVatCalculator</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">countryCodesThatNotShouldBeChargedVat</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">GB</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">NO</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">TR</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Repository</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">config</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?</span><span style="color: #8FBCBB">BaseVatCalculator</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">baseVatCalculator</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">baseVatCalculator</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">baseVatCalculator</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">BaseVatCalculator</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">config</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">baseVatCalculator</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">setBusinessCountryCode</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">BE</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">isValid</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">isValidVatNumber</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">details</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getVATDetails</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">areEqualCountryCodes</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">details</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">countryCode</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">details</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">valid</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">InvalidVatIdOrCountryCodeException</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ViesUnavailableException</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// VIES is unavailable. Consider the VAT ID valid anyways.</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">isValidVatNumber</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">details</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getVatDetails</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">details</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">valid</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">InvalidVatIdOrCountryCodeException</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getTaxRateForLocation</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">zip</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">float</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">shouldCollectVAT</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">in_array</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">countryCodesThatNotShouldBeChargedVat</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Edge case for GB after Brexit &amp; other countries</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatPercent</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">baseVatCalculator</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getTaxRateForLocation</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">zip</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> company</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatPercent</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">100</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">isValidVAT</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">isValid</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatDetails</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">getVatDetails</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ViesUnavailableException</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">isValidVAT</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// API did not respond</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatPercent</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">baseVatCalculator</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getTaxRateForLocation</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">zip</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">isValidVAT</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatPercent</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">100</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">getVatDetails</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">object</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheKey</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">vat_details.</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">vatId</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">details</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Cache</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">remember</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheKey</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #8FBCBB">CarbonInterval</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">week</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">baseVatCalculator</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getVATDetails</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">VATCheckUnavailableException</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getMessage</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">INVALID_INPUT</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">InvalidVatIdOrCountryCodeException</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #ECEFF4">                    </span><span style="color: #616E88">// Anything else (might) mean VIES service is actually just unavailable</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ViesUnavailableException</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">exception</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">getMessage</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">is_object</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">details</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Base VAT calculator caught a VatServiceUnavailableException. Consider VAT ID invalid and invalidate cache.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #8FBCBB">Cache</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">forget</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">cacheKey</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">throw</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">InvalidVatIdOrCountryCodeException</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">make</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatId</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">details</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">shouldCollectVAT</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">in_array</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">countryCodesThatNotShouldBeChargedVat</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">baseVatCalculator</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">shouldCollectVAT</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">country</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>And finally, here is the command the will validate VAT numbers that have not been validated yet. We'll also validate VAT numbers again every 30 days after they validate. This way, when a VAT number gets retracted, we'll also know it.</p>
<p>If a VAT number wasn't valid, we'll send a notification to our Slack channel.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Subscription</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Commands</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Subscription</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Mails</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">VatIdCouldNotBeVerifiedOrIsInvalidMail</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Subscription</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">VatCalculation</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">InvalidVatIdOrCountryCodeException</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Subscription</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">VatCalculation</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">VatCalculator</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Subscription</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">VatCalculation</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ViesUnavailableException</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Team</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Console</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Command</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Log</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Mail</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">RetryValidatingVatIdCommand</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Command</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">signature</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare:retry-validating-vat-id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamsWithUnvalidatedVatIds</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Team</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">whereNotNull</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">vat_id</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">filter</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">vatIdNeedsToBeRevalidated</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">filter</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">VatCalculator</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">shouldCollectVat</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">billing_country</span><span style="color: #ECEFF4">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">map</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">retryValidation</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">filter</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">vatIdHasBeenValidated</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">each</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">fn</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Log</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">channel</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">slack</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">critical</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">😬 VAT ID `</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">vat_id</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">` of team `</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">name</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">` has not been validated</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamsWithUnvalidatedVatIds</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #8FBCBB">Mail</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">queue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">VatIdCouldNotBeVerifiedOrIsInvalidMail</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamsWithUnvalidatedVatIds</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">comment</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">All done!</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">retryValidation</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Team</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatCalculator</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">app</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">VatCalculator</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">billing_country</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatCalculator</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">shouldCollectVAT</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">billing_country</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">markAsVatIdNotValidated</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">validationResult</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">vatCalculator</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isValidVATNumber</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">vat_id</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">validationResult</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">markAsVatIdValidated</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ViesUnavailableException</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Vies is unavailable. Ignore for now.</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">InvalidVatIdOrCountryCodeException</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">markAsVatIdNotValidated</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>It sure is quite a lot of code to get VAT handling right. We considered creating our own VAT calculator package. We held off that plan as Stripe recently announced <a href="https://stripe.com/en-be/tax">a new product, called Stripe Tax, that can calculate VAT</a>. We hope that Spark will offer support for Stripe Tax as soon as Stripe makes its new production publicly available.</p>
<h2>In closing</h2>
<p>By removing our custom UI for billing, we could make our codebase about 12,5K lines lighter.</p>
<blockquote>
<p>✨ <a href="https://t.co/IEFWJgl4Fq">https://t.co/IEFWJgl4Fq</a> now uses the latest version of Spark ✨<br/><br/>🥰 This allowed us to remove 12,5K lines of our code base <a href="https://t.co/VJOkvycLAJ">pic.twitter.com/VJOkvycLAJ</a>— Freek Van der Herten 🔭 (@freekmurze) <a href="https://twitter.com/freekmurze/status/1413106753244434435?ref_src=twsrc%5Etfw">July 8, 2021</a></p>
</blockquote>
<p>Thanks to Spark's latest version, we now have a beautiful billing portal that we don't have to maintain ourselves. We're very grateful for the effort that the Laravel team put into this product.</p>
<p>In this blog post, we only touched upon a few aspects of the migration. If you want to read more details about upgrading Spark and JetStream, head over to <a href="https://freek.dev/1912-how-to-customize-jetstream-and-laravel-spark">this blog post</a> on how our sibling service <a href="https://ohdear.app">Oh Dear</a> was upgraded.</p>
]]>
            </summary>
                                    <updated>2021-08-02T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Refactoring our search capabilities using Elasticsearch and two new Spatie packages]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/refactoring-our-search-capabilities-using-elasticsearch-and-two-new-spatie-packages" />
            <id>https://flareapp.io/refactoring-our-search-capabilities-using-elasticsearch-and-two-new-spatie-packages</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>At Flare, we're continously making improvements to our code base. A lot of the code has gone through multiple iterations and improvements. A part of our code base that was in need of some love was the errors &amp; occurrences search. The original code was a Frankenstein mix of a deprecated Eloquent search query package and ElasticSearch with some custom SQL sprinkled on top.</p>
<p>The past few weeks we've spent time refactoring this code. Recentely, we deployed our <code>improved-search</code> branch to production. Let's dive into the changes in this refactor and the new packages that were born from it.</p>
<h2>A re-usable strategy for search</h2>
<p>Flare features three search fields throughout the application: one for projects, one for errors and one for error occurrences. The biggest annoyance for us was that all three of these search solutions were built differently.</p>
<p>The project search is a simple <code>WHERE LIKE</code> SQL query to filter on project name. It's basically a one-liner, so we left it that way.</p>
<p>The errors and error occurences search on the other hand are both complex search fields with optional filters, grouping and fuzzy filtering on multiple fields. In true DRY (don't repeat yourself) spirit we decided to build a unified search solution for both of these using ElasticSearch.</p>
<p>Additionally, by open-sourcing this solution we've extracted a lot of the complicated search code from our codebase into a package. This isolated package code is easier to test and easier to re-use by other developers. In turn, we get valueable feedback and free bugfixes in the form of PRs.</p>
<h2>ElasticSearch using a custom query language</h2>
<p>When talking about search, it's hard to ignore ElasticSearch. Having used it before for error occurrences we quickly decided to start indexing errors in ElasticSearch as well and fully rely on it as our search driver. ElasticSearch can handle most complex search queries you can come up with, if you know how to formulate it in a JSON search request.</p>
<p>Sadly, having a search field that only takes JSON is bad UX. We still need a way to convert a query string like <code>class:QueryException status 404 group_by:user_id</code> to the JSON request that ElasticSearch can understand. This is where the <a href="https://github.com/spatie/elasticsearch-search-string-parser">spatie/elasticsearch-search-string-parser</a> package comes in handy.</p>
<h2>Introducing spatie/elasticsearch-search-string-parser</h2>
<p>Our new <a href="https://github.com/spatie/elasticsearch-search-string-parser">spatie/elasticsearch-search-string-parser</a> package can dissect a search string like the example above into different &quot;directives&quot; like <code>class=QueryException</code>, <code>grouping=user_id</code> and the remaining <code>status 404</code> for fuzzy filtering.</p>
<p>These directives can be dynamically defined and applied to a search string. Dissecting or parsing these directives works using regular expressions. This allows us to match basic directives like <code>/class:(.*)/</code> but also more complex syntax if we wanted too.</p>
<p>Finally, every directive also knows how to add itself to an <a href="https://github.com/spatie/elasticsearch-query-builder">ElasticSearch query builder</a>. For example, the class directive (<code>class=QueryException</code>) will add the following match query to the ElasticSearch builder:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span></span>
<span class="line"><span style="color: #D8DEE9FF">  query: </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">match</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">	  </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">class_name</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">query</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">QueryException</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">      </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<h2>Implementing search-string-parser in Flare</h2>
<p>Using the search-string-parser package, it suddenly becomes really easy to replace both search controllers in Flare with two <code>SearchQuery</code>s and a couple directives. We even managed to re-use a couple of directives as both the errors and occurrences have a <code>ClassNameDirective</code> to search by exception class and a <code>MessageDirective</code> to search by exception message.</p>
<p>Finally, the search-string-parser packages uses another Spatie package under the hood: the <a href="https://github.com/spatie/elasticsearch-query-builder">elasticsearch-query-builder</a>. It's features a fluent (Eloquent-like) API to to build ElasticSearch queries. It even comes with pagination support out of the box. This means it plugged right in to our custom paginator in Flare.</p>
<h2>Future plans</h2>
<p>Spoilers ahead! As we've now got access to the full power of ElasticSearch in both of our most important search fields, you'll see some cool search features coming up soon. One of the things we're most excited about is auto-completion on the directives in the search field. More on that soon™️!</p>
]]>
            </summary>
                                    <updated>2021-07-20T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Collecting metrics for Flare using event sourcing and laravel-stats]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/collecting-metrics-for-flare-using-event-sourcing-and-laravel-stats" />
            <id>https://flareapp.io/collecting-metrics-for-flare-using-event-sourcing-and-laravel-stats</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Like most SaaS companies, the Flare back-office features a neat dashboard showing some of our key metrics. Using Laravel Nova, it's pretty easy to aggregate data per model like the total number of errors or the active subscriptions per week. Behind the scenes, these dashboard tiles and graphs use simple database queries. For example, the number of active subscriptions is a simple <code>SELECT COUNT(*)</code> query on the <code>subscriptions</code> table. But how do we query new subscriptions per week?</p>
<h2>Counting new subscriptions per week</h2>
<p>Let's take a look at a possible solution for the &quot;new subscriptions per week&quot; metric. Using <a href="https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_yearweek"><code>YEARWEEK()</code></a> we can easily count the active subscriptions grouped by week of the year. The underlying Eloquent query now looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">numberOfActiveSubs</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Subscriptions</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">selectRaw</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">COUNT(*) as number</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">status</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">active</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">groupByRaw</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">YEARWEEK(created_at)</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>This works pretty well. With the correct indexes it's pretty fast too. However, there's one fatal flaw: once a subscription ends, it's no longer counted as a new subscription in the past weeks because it's no longer <code>active</code>. You can probably think of a couple more examples where aggregating metrics from a traditional active record database goes wrong. Here are a couple examples of our own:</p>
<ul>
<li>
<p>the number of active subscriptions per month</p>
</li>
<li>
<p>the number of deleted users</p>
</li>
<li>
<p>the total amount of errors received by a team, including errors outside of the retention period</p>
</li>
</ul>
<p>All of these metrics are based of a certain state in time that's no longer active or data that was only in the database temporarily. If we want to query these metrics at any time, we'll need to keep all relevant data and states forever. In practice, this is less daunting than it sounds. Let's look at three possible solutions (including our very own <a href="https://github.com/spatie/laravel-stats">laravel-stats</a> package) to solve this issue.</p>
<h2>Solution 1: snapshotting metrics</h2>
<p>As explained above, the query for the <em>current</em> number of active subscriptions works pretty well because it relies on the current state of the data. Using a CRON job we can query the current number of active subscriptions every day and save that in a separate <code>active_subscriptions_per_day</code> tabel. This way we're keeping a record of daily active subscriptions by snapshotting the data every day.</p>
<p>However, this method doesn't scale well if you need metrics from different time periods. If you're only storing new sign-ups per day, there's simply no way to query new sign-ups per hour. In other cases you might also miss data that happens in between two snapshots. For example an error that's created an deleted on the same day will never count as a &quot;reported error&quot; for that day. We cannot snapshot data if it's already deleted or modified at the end of the day.</p>
<h2>Solution 2: event sourcing everything</h2>
<p>If we simply store <em>everything</em> that happens in our application, we'll never run into a situation where there's not enough data to calculate a metric. The <a href="https://event-sourcing-laravel.com">event sourcing pattern</a> does exactly that: every change in the applications state is stored and processed as an event. Using these events we can even rebuild the application's state at any point in time and thus calculate metrics at any point in time.</p>
<p>Event sourcing is a pretty powerfull pattern but it can't easily be implemented in existing applications without some overhead. That's why we've opted for a hybrid solution: event sourced metrics without event sourcing.</p>
<h2>Solution 3: event sourced statistics using laravel-stats</h2>
<p>Using the <a href="https://github.com/spatie/laravel-stats">laravel-stats package</a> we can track changes to certain metrics based of the events that cause these changes. For the previous subscriptions example, we can easily created a <code>SubscriptionStats</code> class that can be used to track changes to subscriptions:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Stats</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">BaseStats</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SubscriptionStats</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">BaseStats</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"></span></code></pre>
<p>When a new subscription is activated, we can call <code>SubscriptionStats::increase()</code> to increase the number of active subscriptions. Likewise, when a subscription expires or gets cancelled, we can call the <code>SubscriptionStats::decrease()</code> method to update the number of active subscriptions. Behind the scenes, the package isn't just storing a fixed number of active subscription. Instead, it's storing the <code>increase</code> and <code>decrease</code> events that cause this number to go up or down. You can see this happen in the <code>stats_events</code>:</p>
<p><img src="https://content.spatie.be/assets/flare/collecting-metrics-for-flare-using-event-sourcing-and-laravel-stats/stats.png" alt="Laravel stats inner workings" /></p>
<p>As you can see, we're also storing timestamps for these events. Using these timestamps we can aggregate events and metrics for every timeframe you can think of. For the subscriptions per week metric, this looks a little something like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Stats</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">StatsQuery</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stats</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">StatsQuery</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">for</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SubscriptionStats</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">start</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">subWeeks</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">2</span><span style="color: #ECEFF4">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">end</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">subSecond</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">groupByWeek</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>We're also specifying a <code>start</code> and <code>end</code> date. These boundaries are especially usefull when creating graphs. Finally, the returned data looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">start</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2020-01-01</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">end</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2020-01-08</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">value</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">102</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">increments</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">32</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">decrements</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">20</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">difference</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">12</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">start</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2020-01-08</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">end</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2020-01-15</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">value</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">114</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">increments</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">63</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">decrements</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">30</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">difference</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">33</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>The returned data doesn't only include the summed increments and decrements but it also keeps track of the total number of subscriptions and the difference between each group! This way you can metrics and graphs for, e.g. new teams, deleted teams and the total number of teams, all from one statistics class.</p>
<p>Intrigued? Take a look at the package's <a href="https://github.com/spatie/laravel-stats">readme on GitHub</a> or read more about it on <a href="https://freek.dev/1957-a-lightweight-laravel-package-to-track-changes-over-time">Freek's blog</a>.</p>
]]>
            </summary>
                                    <updated>2021-07-13T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Control exceptions and errors send to Flare]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/control-exceptions-and-errors-send-to-flare" />
            <id>https://flareapp.io/control-exceptions-and-errors-send-to-flare</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>By default, Ignition and the Flare PHP client (if you're using Flare in a non-Laravel application) will always send all the exceptions and errors to Flare.</p>
<p>In the cases where you don't want all exceptions being sent to Flare, you can from now on filter the exceptions sent using a callback:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// in ignition</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">filterExceptionsUsing</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Throwable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">throwable</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">!$</span><span style="color: #D8DEE9">throwable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">instanceof</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">AuthorizationException</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// in the flare-php-client</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">flare</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">filterExceptionsUsing</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">fn</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Throwable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">throwable</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">=&gt;</span><span style="color: #D8DEE9FF">  </span><span style="color: #81A1C1">!$</span><span style="color: #D8DEE9">throwable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">instanceof</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">AuthorizationException</span></span>
<span class="line"><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>When a PHP error is thrown, you can now configure which levels of errors should be sent to Flare:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// in ignition</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">reportErrorLevels</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">E_ALL</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~E_NOTICE</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// in the flare-php-client</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">flare</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">reportErrorLevels</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">E_ALL</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&amp;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">~E_NOTICE</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>In this case, Flare will send all PHP's <a href="https://www.php.net/manual/en/class.error">internal errors</a> except <code>E_NOTICE</code> errors.</p>
]]>
            </summary>
                                    <updated>2021-07-05T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Retry requests using curl]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/retry-requests-using-curl" />
            <id>https://flareapp.io/retry-requests-using-curl</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>We've added a nice addition to our UI. On the &quot;Request&quot; tab of every error. You'll now see a curl command that will resend the request that triggered the error.</p>
<p><img src="https://content.spatie.be/assets/flare/retry-requests-using-curl/curl.jpg" alt="screenshot" /></p>
<p>By executing that command, the request will be resent, allow you to verify if the error is already resolved or not.</p>
]]>
            </summary>
                                    <updated>2021-07-05T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Hello there, new API!]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/hello-there-new-api" />
            <id>https://flareapp.io/hello-there-new-api</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Flare is built for developers. One thing that developers like to do is integrate things. Whenever a service offers an API, it's a bet that developers will use it to build cool things.</p>
<p>Behind the scenes, Flare always had an API for people that requested access. We've now polished that API and made it possible for every user to use it.</p>
<p>To get started, you can create an API token in <a href="/account/api-tokens">your account settings</a>.</p>
<p><img src="https://content.spatie.be/assets/flare/hello-there-new-api/api-tokens.png" alt="screenshot" /></p>
<p>You can use that token to send requests to https://flareapp.io/api. There are endpoints to retrieve information around projects and errors. You can also mark errors as resolved.</p>
<p>To know more, head over to the docs on <a href="/docs/general/using-the-api">using the API</a>.</p>
]]>
            </summary>
                                    <updated>2021-06-28T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Actions can now be performed on all project errors in one go]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/actions-can-now-be-performed-on-all-project-errors-in-one-go" />
            <id>https://flareapp.io/actions-can-now-be-performed-on-all-project-errors-in-one-go</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When viewing all project errors, you can select specific errors that you want to resolve, snooze or delete in one go.</p>
<p><img src="https://content.spatie.be/assets/flare/actions-can-now-be-performed-on-all-project-errors-in-one-go/select-single.png" alt="screenshot" /></p>
<p>Up until now, you could only select errors on the same page of the list. This means that you could only perform these bulk actions for a maximum of 10 errors.</p>
<p>We've removed this limit. When you now check &quot;Select all&quot;, all errors of a project can be selected.</p>
<p><img src="https://content.spatie.be/assets/flare/actions-can-now-be-performed-on-all-project-errors-in-one-go/select-all.png" alt="screenshot" /></p>
<p>This allows all errors of a project to be snoozed, resolved or deleted at once.</p>
<p>You can see other improvements we recently made <a href="https://flareapp.io/changelog">on our changelog</a>. Do you have an idea to improve Flare? <a href="https://github.com/spatie/flareapp.io-roadmap">Let us know</a>!</p>
]]>
            </summary>
                                    <updated>2021-06-21T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Why and how we remove inactive users and teams]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/why-and-how-we-remove-inactive-users-and-teams" />
            <id>https://flareapp.io/why-and-how-we-remove-inactive-users-and-teams</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Like many others SaaS applications, you can try out Flare  using a trial period. Of course, not everybody that tries out Flare will convert. After the trial period is over, some people will not use the service anymore.</p>
<p>If nothing is being done about it, email addresses and other pieces of (personal) data remain in our database. We've recently added clean up procedure to Flare, which will delete all inactive users and teams.</p>
<p>In this blog post we'd like to tell you more about it. We'll also share some actual code from our code base that performs the cleanup.</p>
<h2>Why we remove inactive users and teams</h2>
<p>There are two reasons why we want to delete old users and teams. First, we want to keep your database as small as possible. This has many small benefits. Our queries might execute faster. Our backup process will be shorter. Less disk space is needed... Granted, these things might be a micro-optimizations, as databases are very optimized in performing queries in even large datasets, and the disk space used is probably not too much. Still, the less work our server needs to do, the better.</p>
<p>The second reason is more important: we only want to keep as little personal data as needed for privacy reasons. We only collect and keep the absolute minimum of data needed to run our application. This keeps our user's privacy safe and minimizes the risks for us as a company if a security breach happens.</p>
<p>If people, a few months after a trial period, didn't subscribe, then it's unlikely that they'll subscribe to Flare. We don't need their data anymore.</p>
<h2>How we take care of deleting users and teams in Flare</h2>
<p>We didn't want the cleanup process to be a one time action but a continuous process. The process is implemented as a couple of Artisan commands which are scheduled to run every day.</p>
<h3>Deleting unverified users</h3>
<p>Let's start with the users. Before new users can use Flare, they should verify their email address. This verification is <a href="https://laravel.com/docs/master/verification#introduction">handled by Laravel</a>. There are a lot of users that never verify their email address. I'm assuming that most of them are bots, together with a couple of people that changed their mind about using our service.</p>
<p>Those unverified users have no way of using the application, so it's safe to delete them.</p>
<p>Here's the command that will delete all unverified users ten days after they have been created.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Commands</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Console</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Command</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DeleteOldUnverifiedUsers</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Command</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">signature</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare:delete-old-unverified-users</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Deleting old unverified users...</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">count</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">whereNull</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">email_verified_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">created_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">&lt;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">subDays</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">))</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">delete</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">comment</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Deleted </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">count</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C"> unverified users.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">All done!</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>The most straightforward way to test the command above would be to</p>
<ul>
<li>
<p>seed a user that should be deleted,</p>
</li>
<li>
<p>seed another one that shouldn't be deleted</p>
</li>
<li>
<p>call the command above</p>
</li>
<li>
<p>verify if the command only deleted the right user</p>
</li>
</ul>
<p>Instead of that approach, I prefer scenario-based tests that more closely mimic what happens in real life. In the test below, a user is seeded, and by using <a href="https://github.com/spatie/test-time">the spatie/test-time package</a>, we modify the time.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> Tests</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Feature</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Commands</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">DeleteOldUnverifiedUsers</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">TestTime</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Tests</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">TestCase</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DeleteOldUnverifiedUsersTest</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">TestCase</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setUp</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">parent::</span><span style="color: #88C0D0">setUp</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">freeze</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Y-m-d H:i:s</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2021-01-01 00:00:01</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** @test */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">it_will_delete_unverified_users_after_some_days</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">factory</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">email_verified_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">addDays</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">artisan</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">DeleteOldUnverifiedUsers</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertTrue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">addSecond</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">artisan</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">DeleteOldUnverifiedUsers</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertFalse</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** @test */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">it_will_not_delete_verified_users</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">factory</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">email_verified_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">addDays</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">20</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">artisan</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">DeleteOldUnverifiedUsers</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertTrue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<h3>Deleting inactive teams</h3>
<p>We currently consider a team inactive when it has never subscribed, and the trial period ended more than three months ago. Deleting inactive teams is slightly more complicated. If a team exists, that means that a verified user, the team owner, has created and configured it.</p>
<p>Because there is a chance that the team owner might want to reactivate the team at some point in the future, we don't want to delete the team immediately. Instead, we're going to mail the team owners and give them a chance to cancel the automatic deletion. If no response comes in after a week, we'll delete the team.</p>
<p>Let's take a look at the code. On our <code>teams</code> table, we added two columns.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Database</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Migrations</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Migration</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Database</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Schema</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Blueprint</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Schema</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">AddDeletionMarkerColumnsToTeamsTable</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Migration</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">up</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Schema</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">table</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">teams</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Blueprint</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">timestamp</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">deleting_soon_mail_sent_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">nullable</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">table</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">timestamp</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">automatically_delete_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">nullable</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p><code>deleting_soon_mail_sent_at</code> will contain the datetime when we mailed the automatic deletion notice to the team. <code>automatically_delete_at</code> will contain the date on which the team is scheduled to be deleted.</p>
<p>Here's the <code>shouldBeMarkedForDeletion</code> function that was added to the <code>Team</code> model.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">shouldBeMarkedForDeletion</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">hasActiveSubscription</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">     </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">created_at</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addMonths</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">3</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isPast</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Here's the command that sends out the automatic deletion notices.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Commands</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">MarkInactiveTeamForDeletionSoonAction</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Team</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Console</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Command</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MarkInactiveTeamsForDeletionCommand</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Command</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">signature</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare:mark-inactive-teams-for-deletion</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Starting marking inactive teams for deletion</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">markedAsDeletionCount</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Team</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">each</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">&amp;$</span><span style="color: #D8DEE9">markedAsDeletionCount</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">shouldBeMarkedForDeletion</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">comment</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Marking team </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">name</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C"> (</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">) for deletion</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MarkInactiveTeamForDeletionSoonAction</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">markedAsDeletionCount</span><span style="color: #81A1C1">++;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Marked </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">markedAsDeletionCount</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C"> teams for deletion!</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">All done!</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Here's the code of the <code>shouldBeMarkedForDeletion</code>method  that will determine whether a team should be mailed an automatic deletion notice.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// on the Team model</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">shouldBeMarkedForDeletion</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">   </span><span style="color: #616E88">// if we&#39;ve already sent the mail we don&#39;t want to resend it</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">deleting_soon_mail_sent_at</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">!==</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">hasActiveSubscription</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">wasSubScribedAtSomePointInTime</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">created_at</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addMonths</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">6</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">isPast</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Here's the code of the <code>MarkInactiveTeamForDeletionSoonAction</code> used in the command. If you want to know more about Action classes in general, consider picking up <a href="https://laravel-beyond-crud.com">Laravel Beyond CRUD</a> course where this pattern is explained.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Mails</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">TeamMarkedForDeletionMail</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Team</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Mail</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MarkInactiveTeamForDeletionSoonAction</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">update</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">deleting_soon_mail_sent_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">automatically_delete_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addDays</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">7</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Mail</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">to</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">owner</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">queue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">TeamMarkedForDeletionMail</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">))</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Here's the mail that gets sent to inactive teams.</p>
<p><img src="https://content.spatie.be/assets/flare/why-and-how-we-remove-inactive-users-and-teams/mail-inactive-team.png" alt="Mail" /></p>
<p>To cancel the deletion, team owners should subscribe. Let's take a look at that delete cancellation code.</p>
<p>We have an event listener that executes when a team subscribes. It will set <code>deleting_soon_mail_sent_at</code> and <code>automatically_delete_at</code> to null so that the automatic deletion is effectively cancelled.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CancelAutomaticDeletion</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">SubscriptionCreated</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">event</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #ECEFF4">\</span><span style="color: #616E88">App</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Team</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Team</span><span style="color: #616E88"> $team */</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">event</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">billable</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">update</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">           </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">deleting_soon_mail_sent_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">           </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">automatically_delete_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Now let's take a look at the command that does the actual deletion if team owners don't subscribe. It's pretty simple; it only has to consider the value of <code>automatically_delete_at</code> on a team.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Commands</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">DeleteInactiveTeamAction</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Team</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Console</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Command</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DeleteInactiveTeamsCommand</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Command</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">signature</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare:delete-inactive-teams</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Start deleting old teams...</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Team</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">automatically_delete_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">&lt;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">each</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">comment</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Deleting team </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">id</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DeleteInactiveTeamAction</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">All done!</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Here's the code of the <code>DeleteInactiveTeamAction</code> class used in the command above. In the action, we'll delete the team. We'll also delete the owner if it has no other teams.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Actions</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Team</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DeleteInactiveTeamAction</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">execute</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Team</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamOwner</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">owner</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">team</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">delete</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #ECEFF4">\</span><span style="color: #616E88">App</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Team</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">User</span><span style="color: #616E88"> $teamOwner */</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamOwner</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamOwner</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">refresh</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamOwner</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">allTeams</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">count</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">===</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamOwner</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">delete</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Let's now test that all the code above is working as intended. Again, we're going to use the <a href="https://github.com/spatie/test-time">TestTime</a> class to create a &quot;scenario&quot; test where we move forward in time.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> Tests</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Feature</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Commands</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">DeleteInactiveTeamsCommand</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Commands</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Cleanup</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">MarkInactiveTeamsForDeletionCommand</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Mails</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">TeamMarkedForDeletionMail</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Team</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Team</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Mail</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Spatie</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">TestTime</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Tests</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Factories</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">TeamFactory</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Tests</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">TestCase</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">AutomaticTeamDeletionTest</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">TestCase</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">setUp</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">parent::</span><span style="color: #88C0D0">setUp</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">freeze</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Y-m-d H:i:s</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2020-01-01 00:00:00</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Mail</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">fake</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** @test */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">it_will_delete_a_team_when_it_has_never_subscribed_and_is_older_than_6_months</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #ECEFF4">\</span><span style="color: #616E88">App</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Team</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Team</span><span style="color: #616E88"> $teamWithoutSubscription */</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamWithoutSubscription</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Team</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">factory</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">create</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">addMonths</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">6</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">artisan</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">MarkInactiveTeamsForDeletionCommand</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertFalse</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamWithoutSubscription</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">markedForDeletion</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Mail</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">assertNothingQueued</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">addSecond</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">artisan</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">MarkInactiveTeamsForDeletionCommand</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertTrue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamWithoutSubscription</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">refresh</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">markedForDeletion</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Mail</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">assertQueued</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">TeamMarkedForDeletionMail</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertEquals</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addDays</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">7</span><span style="color: #ECEFF4">),</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamWithoutSubscription</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">refresh</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">automatically_delete_at</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">addDays</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">7</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">artisan</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">DeleteInactiveTeamsCommand</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertTrue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamWithoutSubscription</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">addSecond</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">artisan</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">DeleteInactiveTeamsCommand</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertFalse</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamWithoutSubscription</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/** @test */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">it_will_not_mark_teams_that_have_subscription_for_deletion</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamWithSubscription</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">TeamFactory</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">createSubscribedTeam</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">addMonths</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">6</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addSecond</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">artisan</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">MarkInactiveTeamsForDeletionCommand</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertFalse</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamWithSubscription</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">refresh</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">markedForDeletion</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Mail</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">assertNothingQueued</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">TestTime</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">addDays</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">7</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">addSecond</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">artisan</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">DeleteInactiveTeamsCommand</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">assertTrue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">teamWithSubscription</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">exists</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<h2>In closing</h2>
<p>We hope you like this little tour of how we delete inactive users and teams. If you work on a similar application, We highly encourage you to make sure the old user data is being deleted.</p>
]]>
            </summary>
                                    <updated>2021-06-14T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Meet the new projects overview with error trends and favourites]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/meet-the-new-projects-overview-with-error-trends-and-favourites" />
            <id>https://flareapp.io/meet-the-new-projects-overview-with-error-trends-and-favourites</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Up until a few days ago our projects overview  only showed you the name of the project, some basic error counts and when the latest error came.</p>
<p>A few days ago we launched a vastly improved version of the project overview. You'll immediately notice that for each project we now display the trend of incoming error occurrences.</p>
<p>Here's what it looks like (screenshot taken from our own account, I've blurred out client projects).</p>
<p><img src="https://content.spatie.be/assets/flare/meet-the-new-projects-overview-with-error-trends-and-favourites/project-trends.png" alt="screenshot" /></p>
<p>Using the graph you can quickly see which projects are currently sending more errors than usual.</p>
<p>A second new feature we've introduced is the ability to mark a project as favourite. You can do that by clicking the little star icon at the end of a row in the list. When you favourite a project, it will appear at the top of the list in a separate card.</p>
<p>Here's what that looks like:</p>
<p><img src="https://content.spatie.be/assets/flare/meet-the-new-projects-overview-with-error-trends-and-favourites/project-favourite.png" alt="screenshot" /></p>
<p>Of course, you can favourite as many projects as you like.</p>
<p>You can see other improvements we recently made <a href="https://flareapp.io/changelog">on our changelog</a>. Do you have an idea to improve Flare? <a href="https://github.com/spatie/flareapp.io-roadmap">Let us know</a>!</p>
]]>
            </summary>
                                    <updated>2021-06-07T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Create a GitHub issue directly from an email notification]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/create-a-github-issue-directly-from-an-email-notification" />
            <id>https://flareapp.io/create-a-github-issue-directly-from-an-email-notification</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>A while ago, we added actions links to our mail notifications that allow you to <a href="https://flareapp.io/blog/13-mail-notifications-now-allow-to-snooze-and-resolve-errors">snooze and resolve errors</a>.</p>
<p>We've added an extra action: you can now create a GitHub issue directly from a mail.</p>
<p>Here's how such a link looks like in a mail.</p>
<p><img src="https://content.spatie.be/assets/flare/create-a-github-issue-directly-from-an-email-notification/github-link.png" alt="screenshot" /></p>
<p>When click that link, an issue will be created on GitHub.</p>
<p><img src="https://content.spatie.be/assets/flare/create-a-github-issue-directly-from-an-email-notification/github.png" alt="screenshot" /></p>
<p>We hope that you will like this feature.</p>
]]>
            </summary>
                                    <updated>2021-05-31T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[How to censor sensitive information in requests to Flare]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-to-censor-sensitive-information-in-requests-to-flare" />
            <id>https://flareapp.io/how-to-censor-sensitive-information-in-requests-to-flare</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When an exception occurs in a web request, the Flare client will pass on all request fields that are present in the body.</p>
<p>In some cases, such as a login page, these request fields may contain a password that you don't want to send to Flare. By default, Flare will replace the value any fields that are named &quot;password&quot; with &quot;&lt;CENSORED&gt;&quot;.</p>
<p>You can censor values of additional fields. If you use Laravel, you can put the names of those fields in the <code>reporting.censor_request_body_fields</code> key of the <code>flare</code> config file.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// config/flare.php</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// ...</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">reporting</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// ...</span></span>
<span class="line"><span style="color: #D8DEE9FF">       </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">censor_request_body_fields</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">other_field</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF">   </span></span>
<span class="line"><span style="color: #ECEFF4">]</span></span>
<span class="line"></span></code></pre>
<p>In non-Laravel PHP projects you can use call <code>censorRequestBodyFields</code> on the Flare client. You should pass it the names of the fields you wish to censor.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// Where you registered your client...</span></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">flare</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">YOUR-FLARE-API-KEY</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">registerFlareHandlers</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">flare</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">censorRequestBodyFields</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">password</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>This will replace the value of any sent fields named &quot;password&quot; with the value &quot;&lt;CENSORED&gt;&quot;.</p>
<p>You can see other improvements we recently made <a href="https://flareapp.io/changelog">on our changelog</a>. Do you have an idea to improve Flare? <a href="https://github.com/spatie/flareapp.io-roadmap">Let us know</a>!</p>
]]>
            </summary>
                                    <updated>2021-05-24T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[You can now easily see if an error came from the web, CLI or queue]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/you-can-now-easily-see-if-an-error-came-from-the-web-cli-or-queue" />
            <id>https://flareapp.io/you-can-now-easily-see-if-an-error-came-from-the-web-cli-or-queue</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When an error comes in, it might be handy to know in which type of environment it was thrown. On each error card in our UI, you can now see the &quot;type&quot; of error.</p>
<p><img src="https://content.spatie.be/assets/flare/you-can-now-easily-see-if-an-error-came-from-the-web-cli-or-queue/type.png" alt="screenshot" /></p>
<p>This type can contain &quot;web&quot;, &quot;cli&quot; or &quot;queue&quot;. Sure, it's a small change, but we think this a useful one.</p>
<p>You can see other improvements we recently made <a href="https://flareapp.io/changelog">on our changelog</a>. Do you have an idea to improve Flare? <a href="https://github.com/spatie/flareapp.io-roadmap">Let us know</a>!</p>
]]>
            </summary>
                                    <updated>2021-05-17T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[How our GitHub integration works under the hood]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-our-github-integration-works-under-the-hood" />
            <id>https://flareapp.io/how-our-github-integration-works-under-the-hood</id>
            <author>
                <name><![CDATA[Ruben]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>A few weeks ago, we introduced <a href="https://flareapp.io/blog/23-introducing-our-github-integration">a new integration with GitHub</a> that makes it possible to:</p>
<ul>
<li>
<p>create a GitHub issue directly on a Flare error</p>
</li>
<li>
<p>associate a GitHub issue with a Flare error by mentioning a Flare URL in the GitHub issue</p>
</li>
<li>
<p>automatically resolve an error on Flare when you close the GitHub issue</p>
</li>
<li>
<p>automatically close a GitHub issue when you resolve an error in Flare</p>
</li>
</ul>
<p>In the stream below, our developers <a href="https://twitter.com/rubenvassche">Ruben</a> and <a href="https://twitter.com/freekmurze">Freek</a> will show you how you can use this integration and how it works under the hood.</p>
]]>
            </summary>
                                    <updated>2021-05-10T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing the monthly error report mail]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-the-monthly-error-report-mail" />
            <id>https://flareapp.io/introducing-the-monthly-error-report-mail</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>In our UI, you can see a nice summary of all your projects. The project list mentions the number of errors in the past 30 days and how many are unresolved. Here's how that list looks like for all projects of the Spatie team. The project names are considered sensitive information, so we've rendered them illegible in the screenshot.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-the-monthly-error-report-mail/project-list.png" alt="screenshot" /></p>
<p>We think that most users won't visit this project list too often. Most of the times, you'll use Flare and access the app to handle a specific error.</p>
<p>We decided to email the project overview to each user every month. This is how that mail looks like. Notice that we also include links to what's new at the Flare blog.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-the-monthly-error-report-mail/monthly-mail.png" alt="screenshot" /></p>
<p>If you don't want to receive this monthly mail, you can opt out at <a href="https://flareapp.io/account/profile">your user profile page</a>.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-the-monthly-error-report-mail/profile.png" alt="screenshot" /></p>
<p>You can see other improvements we recently made <a href="https://flareapp.io/changelog">on our changelog</a>. Do you have an idea to improve Flare? <a href="https://github.com/spatie/flareapp.io-roadmap">Let us know</a>!</p>
]]>
            </summary>
                                    <updated>2021-05-03T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing our GitHub integration]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-our-github-integration" />
            <id>https://flareapp.io/introducing-our-github-integration</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>We're proud to share that errors on Flare can now be associated with issues on GitHub and vice versa. This integration allows to:</p>
<ul>
<li>
<p>create a GitHub issue directly on a Flare error</p>
</li>
<li>
<p>associate a GitHub issue with a Flare error by mentioning a Flare URL in the GitHub issue</p>
</li>
<li>
<p>automatically resolve an error on Flare when you close the GitHub issue</p>
</li>
<li>
<p>automatically close a GitHub issue when you resolve an error in Flare</p>
</li>
</ul>
<h2>Getting started</h2>
<p>In team settings you'll find a new page titled &quot;GitHub&quot;. On that page you can click the connect with GitHub button.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-github-integration/github-team-settings.png" alt="screenshot" /></p>
<p>Flare will ask read and write access to your issues and pull requests on your GitHub account.</p>
<p>After you've connected Flare to GitHub, you can connect Flare projects with GitHub repositories. In the project settings, there's a GitHub page. On this page, you can select a repository that should be connected with the project.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-github-integration/project-settings-github.png" alt="screenshot" /></p>
<h2>Associating Flare errors with GitHub issues / PRs</h2>
<p>After you've connected a GitHub repo to a Flare project, you'll see an extra button &quot;Create issue&quot; on each error in Flare.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-github-integration/create-issue.png" alt="screenshot" /></p>
<p>By pressing this button you can quickly create an issue in GitHub.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-github-integration/github-issue.png" alt="screenshot" /></p>
<p>Alternatively, you could manually open an issue at GitHub and mention add a Flare error URL in the issue description or any of the comments. This will also associate the GitHub issue with the Flare error.</p>
<p>When you return to Flare, you can see that the UI now shows that this error is now associated with one GitHub issue.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-github-integration/issue-link.png" alt="screenshot" /></p>
<p>When you click that link, you'll see a card that contains a link to the issue, and you can optionally couple the same error to another issue.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-github-integration/github-menu.png" alt="screenshot" /></p>
<p>You can link a GitHub issue or pull request with an error by adding the URL to the Flare error in the title, body or comments of the issue or pull request.</p>
<h2>Resolving Flare errors and closing GitHub issues / PRs</h2>
<p>Flare will automatically resolve an error when an issue was closed, or a pull request was merged.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-github-integration/close-issue.png" alt="screenshot" /></p>
<p>Optionally, you can let Flare close GitHub issues whenever an error is resolved. You can opt-in for this behaviour by checking the last option at the GitHub screen in the team settings.</p>
<p><img src="https://content.spatie.be/assets/flare/introducing-our-github-integration/github-option.png" alt="screenshot" /></p>
<h2>In closing</h2>
<p>We think GitHub integration is a handy feature for a lot of our users. Internally we've built our integration VCS agnostic. If we get enough feedback from our users that Gitlab or Bitbucket integration is wanted, we'll consider supporting those too.</p>
<p>You can see other improvements we recently made <a href="https://flareapp.io/changelog">on our changelog</a>. Do you have an idea to improve Flare? <a href="https://github.com/spatie/flareapp.io-roadmap">Let us know</a>!</p>
]]>
            </summary>
                                    <updated>2021-04-26T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Mail notifications now allow to snooze and resolve errors]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/mail-notifications-now-allow-to-snooze-and-resolve-errors" />
            <id>https://flareapp.io/mail-notifications-now-allow-to-snooze-and-resolve-errors</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When an error occurs, Flare can notify you via <a href="https://flareapp.io/docs/notifications/configuring-notifications">many different notification channels</a>. Some of these notifications destinations have rich APIs that allow us to add actions to snooze and resolve errors right on the notification. Here's how a Slack notification looks like; notice the action buttons below.</p>
<p><img src="https://content.spatie.be/assets/flare/mail-notifications-now-allow-to-snooze-and-resolve-errors/slack-notification.png" alt="screenshot" /></p>
<p>When we look at which notification channels our users use, we see that mail is the most popular channel. This motivated us to provide snoozing and resolving abilities to mail notifications. We deployed our changes a couple of days ago. In all mail notifications, you'll now see new snooze and resolve links.</p>
<p><img src="https://content.spatie.be/assets/flare/mail-notifications-now-allow-to-snooze-and-resolve-errors/mail-notification.png" alt="screenshot" /></p>
<p>These links will work even if you are not signed in at Flare. This functionality is implemented securely. These action links only have a limited lifetime. Behind the scenes, the links leverage Laravel's <a href="https://opendor.me/@freekmurze">https://laravel.com/docs/master/urls#signed-urls</a>, so they cannot be tampered with.</p>
<p>We think these action links provide a convenient way to snooze/resolve errors quickly. You can see other improvements we recently made <a href="https://flareapp.io/changelog">on our changelog</a>. Do you have an idea to improve Flare? <a href="https://github.com/spatie/flareapp.io-roadmap">Let us know</a>!</p>
]]>
            </summary>
                                    <updated>2021-04-19T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[You can now view spike protection periods]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/you-can-now-view-spike-protection-periods" />
            <id>https://flareapp.io/you-can-now-view-spike-protection-periods</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>There are circumstances in which your application could send many errors to Flare in a short amount of time. <a href="https://flareapp.io/docs/general/spike-protection">Spike protection</a> will prevent all events of your plan being used in a small timespan.</p>
<p>There are circumstances in which your application could send many errors to Flare in a short amount of time. Spike protection will prevent all events of your plan being used in a small timespan.</p>
<p>Some of our users have recently requested to view the periods that Spike protection is active. On the Spike protection page of the project settings you'll now see a table listing all period that spike protection was active. Here's how that looks like for <a href="https://spatie.be">spatie.be</a>.</p>
<p><img src="https://content.spatie.be/assets/flare/you-can-now-view-spike-protection-periods/spike-protection.png" alt="Spike protection" /></p>
<p>You can see other improvements we recently made <a href="https://flareapp.io/changelog">on our changelog</a>. Do you have an idea to improve Flare? <a href="https://github.com/spatie/flareapp.io-roadmap">Let us know</a>!</p>
]]>
            </summary>
                                    <updated>2021-04-12T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Our docs are now searchable]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/our-docs-are-now-searchable" />
            <id>https://flareapp.io/our-docs-are-now-searchable</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>If you head over to <a href="https://flareapp.io/docs">our documentation</a>, then you'll notice that we have added a nice quality of life improvement. There's a new search field that allows you to easily search our entire docs.</p>
<p><img src="https://content.spatie.be/assets/flare/our-docs-are-now-searchable/search.png" alt="screenshots" /></p>
<p>This feature is powered by <a href="https://docsearch.algolia.com">Algolia DocSearch</a>, which is also used on the documentation pages of <a href="https://spatie.be/docs/laravel-backup">our</a> <a href="https://spatie.be/docs/ray/">many</a> <a href="https://spatie.be/docs/laravel-medialibrary">open</a> <a href="https://spatie.be/docs/laravel-dashboard">source</a> <a href="https://spatie.be/docs/laravel-mailcoach">projects</a>.</p>
<p>You can see other improvements we recently made <a href="https://flareapp.io/changelog">on our changelog</a>. Do you have an idea to improve Flare? <a href="https://github.com/spatie/flareapp.io-roadmap">Let us know</a>!</p>
]]>
            </summary>
                                    <updated>2021-04-08T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Snooze notifications per application version]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/snooze-notifications-per-application-version" />
            <id>https://flareapp.io/snooze-notifications-per-application-version</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>In addition to snoozing notifications for a number of occurrences or a fixed period, we have introduced a new way of snoozing errors. You can now snooze errors per application version. In this blog post, you'll learn all about it.</p>
<h2>Snoozing options in Flare</h2>
<p>Flare has powerful options to notify you when something goes wrong in your app. We provide notifications via email, Slack, SMS (through Nexmo), Discord, Microsoft Teams and webhooks.</p>
<p>For each of these channels, you can choose which notifications should be sent through them. These are the events we can notify you of</p>
<ul>
<li>
<p>when an error occurs for the very first time</p>
</li>
<li>
<p>when an error occurs for the 1st, 10th, 100th, 1000th, 2000th, ... time</p>
</li>
<li>
<p>when an error that was marked as resolved, occurs again</p>
</li>
<li>
<p>...</p>
</li>
</ul>
<h2>Snoozing errors per application version</h2>
<p>If you are aware of an error in an application, you might temporary not want to receive notifications concerning a particular error anymore. You do want to get notified again after you've deployed a fix for the error.</p>
<p>You can achieve this by sending the version number of your app whenever an error occurs. In Laravel projects, you can do this by calling <code>determineVersionUsing</code> on the <code>Flare</code> facade.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// in a service provider</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Facade</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Ignition</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">determineVersionUsing</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">function</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">1.0</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// return your version number here.</span></span>
<span class="line"><span style="color: #ECEFF4">})</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>With Flare being aware of your application version, you can now snooze any errors sent to Flare for a particular version.</p>
<p><img src="https://content.spatie.be/assets/flare/snooze-notifications-per-application-version/snooze-version.png" alt="Snooze version" /></p>
<p>You will only get notified when the error comes in with another version number (probably after you've redeployed your app).</p>
<h2>Automatically determining a version number</h2>
<p>If you don't set a version number explicitly using <code>determineVersionUsing</code>, we can use a short commit hash if your application is served via a git repo.</p>
<p>For this to work, you'll need to set the <code>reporting.collect_git_information</code> to value <code>true</code> in the <code>flare</code> config file, so git information is sent to Flare.</p>
]]>
            </summary>
                                    <updated>2021-04-05T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Improving Ignition's security]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/improving-ignitions-security" />
            <id>https://flareapp.io/improving-ignitions-security</id>
            <author>
                <name><![CDATA[Alex]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>A few days ago, you might have received a Dependabot security warning on Ignition. This warning concerns <a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3129">CVE-2021-3129</a>, which warns that <a href="https://github.com/facade/ignition">Ignition</a>, Laravel's default error page, allows unauthenticated, remote attackers to execute arbitrary code.</p>
<p><img src="https://content.spatie.be/assets/flare/improving-ignitions-security/dependabot.png" alt="Dependabot warning" /></p>
<p>In this blog post, we'd like to explain why that security warning isn't an issue for most and how we further improved Ignition's security.</p>
<h2>This exploit only works when debug mode is turned on</h2>
<p>When looking at the description of <a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3129">CVE-2021-3129</a>, you might think that Ignition has a severe security problem. What is not mentioned in the description is that this exploit only worked when debug mode was turned on. The underlying RCE in Ignition has since been fixed (<a href="https://github.com/facade/ignition/pull/334">PR #334</a> and version 2.5.2) and additional security measures were taken to protect people from unknowingly exposing their Laravel apps due to misconfiguration.</p>
<p>Ignition has the ability to <a href="https://flareapp.io/docs/ignition/solutions/runnable-solutions">run executable solutions</a>. These solutions can make your life better by running migrations when you forgot to run them, generating an <code>APP_KEY</code> if you set none, fixing variable names in your code, ... These runnable solutions are only available when Laravel is in <a href="https://laravel.com/docs/8.x/configuration#debug-mode">debug mode</a>.</p>
<p><strong>We highly recommend never to turn on debug mode in a non-local environment. If you do so, then you risk exposing sensitive information and potentially allow outsiders to execute solutions.</strong></p>
<p>To know how Ignition could be exploited when debug mode is on, take a look at <a href="https://www.ambionics.io/blog/laravel-debug-rce">this research report by Ambionics security</a>.</p>
<p>We want to stress that you should never turn on debug mode in a non-local environment.</p>
<h2>How we have improved Ignition in response to this warning</h2>
<p>We think the vast majority of Laravel developers are aware that they should never enable debug mode on a non-local site.</p>
<p>To warn those who don't, Ignition will now display a warning when an error is rendered with debug mode in a non-local environment.</p>
<p><img src="https://content.spatie.be/assets/flare/improving-ignitions-security/ignition-warning.png" alt="Ignition warning screenshot" /></p>
<p>We think that most people do not ever want to run solutions in a production environment.  To protect Ignition users even further, we've downright removed the ability to run solutions in non-local environments and from non-local IP addresses. You can see the changes in <a href="https://github.com/facade/ignition/pull/364">this PR</a>, which is included in <a href="https://github.com/facade/ignition/blob/master/CHANGELOG.md#261---2021-03-30">v2.6.1</a>.</p>
<p>Finally, we've added <a href="https://flareapp.io/docs/ignition/introducing-ignition/security-recommendations">a page on security</a> to the Ignition docs that mention our recommendations.</p>
]]>
            </summary>
                                    <updated>2021-03-31T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Flare can now notify you via Discord and Microsoft Teams]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/flare-can-now-notify-you-via-discord-and-microsoft-teams" />
            <id>https://flareapp.io/flare-can-now-notify-you-via-discord-and-microsoft-teams</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When we launched Flare, we could notify you over errors occurring via Slack, Mail, SMS and Webhooks. When we take a look at our database, Slack seems to be the predominant way for teams to communicate.</p>
<p>In the meantime however, we got a lot of questions to add support for alternative notification platforms. Today, we're adding support for sending notifications via <a href="https://flareapp.io/docs/notifications/discord">Discord</a> and <a href="https://flareapp.io/docs/notifications/discord">Microsoft Teams</a>.</p>
<p>We took great care in formatting exception just right. If your teams uses Flare and Discord, this is how exceptions will look like in the Discord UI.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-can-now-notify-you-via-discord-and-microsoft-teams/discord.png" alt="Screenshot Discord" /></p>
<p>And here's how's it looks like when using Microsoft Teams.</p>
<p><img src="https://content.spatie.be/assets/flare/flare-can-now-notify-you-via-discord-and-microsoft-teams/ms-teams.png" alt="Screenshot Microsoft Teams" /></p>
<p>In our documentation you can read how you can <a href="https://flareapp.io/docs/notifications/configuring-notifications">start using these new notification channels</a>.</p>
<h2>How these notification channels are implemented</h2>
<p>Behind the scenes, Flare is a <a href="https://laravel.com">Laravel</a> application. Out of the box, Laravel has easy to extend system for <a href="https://laravel.com/docs/master/notifications">sending out notifications</a>.</p>
<p>For Discord, we quickly implemented our own Discord notification channel and message.</p>
<p>This is the code of the <code>DiscordChannel</code> class.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Notification</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Services</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Channels</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Discord</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> GuzzleHttp</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Client</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> GuzzleHttp</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">RequestOptions</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Notifications</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Notification</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DiscordChannel</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">send</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">notifiable</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Notification</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">notification</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">discordMessage</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">notification</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toDiscord</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">discordWebhook</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">notifiable</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">routeNotificationForDiscord</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Client</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">post</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">discordWebhook</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #8FBCBB">RequestOptions</span><span style="color: #81A1C1">::</span><span style="color: #D8DEE9FF">JSON </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">discordMessage</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toArray</span><span style="color: #ECEFF4">(),</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>And here's the code for the <code>DiscordMessage</code> class. You'll notice that it's a bit opinionated. For convenience, we've added an <code>errorOccurrence</code> method directly on the class that can format a given <code>ErrorOccurrence</code> model.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Notification</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Services</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Channels</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Discord</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Error</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ErrorOccurrence</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Http</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Controllers</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Projects</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ShowProjectController</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Carbon</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Carbon</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DiscordMessage</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> COLOR_SUCCESS </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">0b6623</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> COLOR_WARNING </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">fD6a02</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> COLOR_ERROR </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">e32929</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">title</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">description</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">fields</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">timestamp</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">footer</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">?string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">color</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">null;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">url</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">title</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">title</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">title</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">title</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">url</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">url</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">url</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">url</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">description</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">descriptionLines</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">is_array</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">descriptionLines</span><span style="color: #ECEFF4">))</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">descriptionLines</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">descriptionLines</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">description</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">.=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">implode</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">PHP_EOL</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">descriptionLines</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">timestamp</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Carbon</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">carbon</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">timestamp</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">carbon</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">toIso8601String</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">footer</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">footer</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">footer</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">footer</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">success</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">color</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">static::</span><span style="color: #D8DEE9FF">COLOR_SUCCESS</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">warning</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">color</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">static::</span><span style="color: #D8DEE9FF">COLOR_WARNING</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">error</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">color</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">static::</span><span style="color: #D8DEE9FF">COLOR_ERROR</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fields</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">array</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">fields</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">inline</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">foreach</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">fields</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">as</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">label</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">fields</span><span style="color: #ECEFF4">[]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">name</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">label</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">value</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">inline</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">inline</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">errorOccurrence</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ErrorOccurrence</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">string</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">message</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">projectUrl</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">action</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">ShowProjectController</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">idSlug</span><span style="color: #ECEFF4">())</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">stage</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">stage</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">(</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">stage</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">)</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;&quot;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">title</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">project</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">name</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C"> </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">stage</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">url</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">projectUrl</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">fileLocation</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">file</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">:`</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">line_number</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">`</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">firstFrame</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">firstFrame</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">fileLocation</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">firstFrame</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">file</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">:`</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">firstFrame</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">lineNumber</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">`</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">properties</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">URL: </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">seen_at_url</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">message</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">[See details on Flare](</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">reportUrl</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">)</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">**</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">exception_message</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">**</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">**File:** </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">fileLocation</span><span style="color: #81A1C1">}</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">firstFrame</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">codeSnippet</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">firstFrame</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">shortenCodeSnippetWithCurrentLineMark</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">occurredIn</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">`</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">firstFrame</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">class</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">::</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">firstFrame</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">method</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">`</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">PHP_EOL;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">fields</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">occurredIn</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">```</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">language</span><span style="color: #81A1C1">}</span><span style="color: #EBCB8B">\n</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">codeSnippet</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">```</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">],</span><span style="color: #D8DEE9FF"> inline</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">fields</span><span style="color: #ECEFF4">([</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Stage</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">stage</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Exception</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">exception_class</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Occurrence count</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">occurrence_count</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Affected users</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">error</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">affected_user_count</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">])</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">description</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">properties</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">timestamp</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9">received_at</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">toArray</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">array</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">username</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Flare</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">avatar_url</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">https://flareapp.io/images/flare-avatar.png</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">embeds</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">title</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">title</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">url</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">url</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">type</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">rich</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">description</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">description</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">fields</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">fields</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">color</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">hexdec</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">color</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">footer</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">text</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">footer</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">timestamp</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">timestamp</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">],</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>With this in place, we can add a <code>toDiscord</code> method on our notification class.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Error</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Notifications</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">ErrorOccurred</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Error</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ErrorOccurrence</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Notification</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Services</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Channels</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Discord</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">DiscordMessage</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">NewErrorOccurredNotification</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Notification</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">__construct</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ErrorOccurrence</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">errorOccurrence</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// other notifications channels omitted for brevity...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">toDiscord</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">DiscordMessage</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">error</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">errorOccurrence</span><span style="color: #ECEFF4">(</span></span>
<span class="line"><span style="color: #D8DEE9FF">	            </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #D8DEE9">errorOccurrence</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	            </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">A new error occurred</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">	        </span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<h2>In closing</h2>
<p>We're very happy with these new notification channels, and hope you'll like them as well. In the near future, you can expect more improvements around Flare's notification sytem.</p>
<p>If you want use to support your favourite notification channels, or have suggestions on how to make Flare better, <a href="mailto:info@flareapp.io">let us know</a>. We're listening!</p>
]]>
            </summary>
                                    <updated>2021-03-30T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Installing Blackfire on Laravel Vapor]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/installing-blackfire-on-laravel-vapor" />
            <id>https://flareapp.io/installing-blackfire-on-laravel-vapor</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Over at Flare we're dealing with a lot of traffic on just one endpoint backed by AWS Lambda. Our base approach here is &quot;store first, process later&quot; (later meaning just seconds later on a async queue). However, <a href="https://aws.amazon.com/blogs/aws/new-for-aws-lambda-1ms-billing-granularity-adds-cost-savings/">now more than ever</a> it's important to keep the reporting endpoint performant.</p>
<p>There are a couple of interesting PHP profilers out there that can help us to to so. Xdebug is probably one of the most wide-spread solutions but for Flare we chose to use Blackfire as it features an easy solution to get production profiling data without being too disruptive.</p>
<p>In this blogpost we'll take a quick look at how Blackfire is set-up and how Lambda layers works. In the second part of this post we'll add the two together to get production profiling data from Lambda into Blackfire.</p>
<h2>Blackfire basics</h2>
<p>If you've ever installed Blackfire on a more traditional server (or even locally) you'll know that there are two important moving parts:</p>
<ol>
<li>
<p>the Blackfire PHP extension or &quot;PHP probe&quot;</p>
</li>
<li>
<p>and the Blackfire agent.</p>
</li>
</ol>
<p>I like to think of the agent as a proxy service to Blackfire's APIs. It reads profiling data from a unix socket or TCP address, does some processing and then sends the data off to our Blackfire account using the configured server ID and token.</p>
<p>The PHP probe is nothing more than a PHP extension. It will read profiling data from the PHP process and send it to the Blackfire agent. That's why its most important configuration option is the <code>BLACKFIRE_AGENT_SOCKET</code> value. This configures how the PHP probe can communicate with the Blackfire agent. This is important because the agent doesn't necessarily run on the same machine as the PHP probe. This will come in handy for getting Blackfire to work on Lambda.</p>
<h2>Intro to Lambda layers</h2>
<p>It's like Shrek once said: &quot;Lambdas are like onions. Onions have layers. Lambdas have layers&quot;. Every layer adds some additional code, libraries or runtimes on top of your Lambda's source code.</p>
<p>Compared to a more traditional Lambda that uses the built-in NodeJS runtime, PHP isn't one of the <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html">default Lambda runtimes</a>. That's why every Laravel Vapor Lambda deploys with a PHP layer that contains the necessary binaries and configuration options to execute your PHP code.</p>
<p>These layers may sound like magic binary files containing some low-level unix voodoo but they're actually just ZIP archives containing files. If you unzip the PHP layer you should see some familiar looking directories containing a PHP binary and various extensions and CLI tools.</p>
<p>Lambda layers are typically used to add extensions to the base runtime. The <code>vapor.yml</code> file can be configured to include up to 4 layers in the application's deployment. A typical use-case for adding layers to Vapor's Lambdas is to <a href="https://blog.laravel.com/vapor-adding-imagick-as-a-separate-lambda-layer">install extensions like Imagick</a>. We'll use it to install the Blackfire probe PHP extension.</p>
<h2>Putting one and two together</h2>
<p>Now we've got the theory out of the way, here's a breakdown of the steps we'll take to install Blackfire on Laravel Vapor:</p>
<ol>
<li>
<p>Get a Blackfire account (this one's obvious)</p>
</li>
<li>
<p>Install the Blackfire agent on a separate EC2 instance</p>
</li>
<li>
<p>Build the Blackfire probe as a Lambda layer</p>
</li>
<li>
<p>Add the Lambda layer to the app's deployment configuration</p>
</li>
<li>
<p>Configure the Blackfire probe layer</p>
</li>
<li>
<p>...Profit (or debug)</p>
</li>
</ol>
<h3>1. Installing the Blackfire agent</h3>
<p>If you skipped the intro to Blackfire you might not know that Blackfire needs more than just a PHP extension to run. It also needs the Blackfire agent to function. Sadly, we can't simply install the agent on a Lambda function. We'll need a separate EC2 instance in the same security group to host the agent.</p>
<p>Luckily, if you're using Laravel Vapor, you might already have a &quot;jump box&quot; set-up to use as a SSH proxy into various services. This jump box is nothing more than a pre-configured EC2 instance that's already set-up to communicate with other services your app might need, including AWS Lambda. This means it's perfect for hosting the Blackfire agent. If you don't already have a jump box, you can create one using the <code>vapor jump</code> command.</p>
<p>For installing the Blackfire agent on this EC2 instance you can follow <a href="https://blackfire.io/docs/up-and-running/installation?action=install&amp;mode=full&amp;location=server&amp;os=debian&amp;language=php">the instructions provided by Blackfire</a>. They go over adding a package repository, installing and configuring the agent and finally starting it. You can skip the part about &quot;Installing the PHP probe&quot; as we'll take a different approach for Lambda.</p>
<p>The next step is to configure the agent to listen to a TCP address instead of the default unix socket. This allows us to connect to the agent from outside of the local machine. You can do this by editing the <code>/etc/default/blackfire-agent</code> config file using <code>nano</code> or <code>vim</code> and changing the <code>SOURCE</code> value to <code>&quot;tcp://0.0.0.0:8307&quot;</code>.</p>
<p>Don't forget to restart the agent afterwards using the following command:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">sudo </span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">etc</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">init</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">d</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">blackfire</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">agent restart</span></span>
<span class="line"></span></code></pre>
<p>Finally we need to configure the EC2 instance to accept incoming connections from any IP on port <code>8307</code>. This can be changed in the section on &quot;inbound rules&quot; for the EC2 instance's security group<br />
(EC2 instance configuration &gt; security tab &gt; clicking through to the security group &gt; edit inbound rules). The resulting rules should include something like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">|-</span><span style="color: #D8DEE9FF">Type</span><span style="color: #81A1C1">-------|-</span><span style="color: #D8DEE9FF">Protocol</span><span style="color: #81A1C1">-|-</span><span style="color: #D8DEE9FF">Port</span><span style="color: #81A1C1">-|-</span><span style="color: #D8DEE9FF">Source</span><span style="color: #81A1C1">----|</span></span>
<span class="line"><span style="color: #81A1C1">|------------|----------|------|-----------|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> Custom TCP </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> TCP      </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">8307</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">0</span><span style="color: #81A1C1">/</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span></span>
<span class="line"><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> Custom TCP </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> TCP      </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">8307</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">::/</span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">      </span><span style="color: #81A1C1">|</span></span>
<span class="line"></span></code></pre>
<h3>2. Building the Blackfire probe Lambda layer</h3>
<p>Neither Blackfire nor Laravel provide a pre-built Blackfire probe extension layer for Vapor. This means we'll have to build our own. Luckily the people behind Bref (another serverless PHP framework) made this rather easy for us.</p>
<p>Start by cloning the <a href="https://github.com/brefphp/extra-php-extensions">brefphp/extra-php-extensions</a> repository from GitHub. Make sure you have Docker running locally and execute the following command to build the Blackfire layer for PHP 7.4:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">make docker</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">images layer</span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF">blackfire php_version</span><span style="color: #81A1C1">=</span><span style="color: #B48EAD">7</span><span style="color: #ECEFF4">.</span><span style="color: #B48EAD">4</span></span>
<span class="line"></span></code></pre>
<p>After a couple of minutes you can thank the Bref team and take the <code>layer-blackfire-php-74.zip</code> file from the export directory.</p>
<h3>3. Deploying a Vapor app with the Blackfire probe layer</h3>
<p>To start using our freshly built layer, we'll have to upload it to AWS first. You can do this in the AWS Lambda service section under &quot;Layers&quot;. Make sure to give it a descriptive name like <code>blackfire-php-74</code> and add the layer's ZIP archive we built in the previous step. You don't need to select any runtimes, description or license info.</p>
<p>Once AWS finishes processing your new layer you can copy its &quot;Version ARN&quot;. This is the identifier we'll have to use in your application's <code>vapor.yml</code> file. Following the Vapor docs we can add our new layer after the default PHP 7.4 layer. Your <code>vapor.yml</code> file should look a little something like this:</p>
<p>`id: 1337<br />
name: flareapp<br />
environments:<br />
production:<br />
runtime: php-7.4<br />
layers:</p>
<ul>
<li>vapor:php-7.4</li>
<li>arn:aws:lambda:eu-central-1:683058659124:layer:blackfire-php74:1</li>
</ul>
<h1>rest of the file...</h1>
<p>`</p>
<p>As you can see, we also included Vapor's own PHP as this is still required to run your codebase.</p>
<h3>4. Configuring the Blackfire probe extension</h3>
<p>At this point you should have the Blackfire agent running on an EC2 instance and the Blackfire probe layer built and added to your Vapor app. The final step is to configure the Blackfire probe extension to connect to the agent on that separate EC2 instance.</p>
<p>Vapor's PHP layer will check the <code>php/conf.d/</code> directory in your app's directory for any additional PHP config files. You can add the following Blackfire config file in <code>php/conf.d/blackfire.ini</code> to configure the probe extension to contact the agent on that external server:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">extension</span><span style="color: #81A1C1">=/</span><span style="color: #D8DEE9FF">opt</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">bref</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">extra</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">blackfire</span><span style="color: #81A1C1">.</span><span style="color: #D8DEE9FF">so</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">;</span><span style="color: #D8DEE9FF"> Replace the following value with your agent</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">s EC2 domain address</span></span>
<span class="line"><span style="color: #A3BE8C">blackfire.agent_socket=tcp://ip-10-0-0-61.eu-central-1.compute.internal:8307</span></span>
<span class="line"></span>
<span class="line"><span style="color: #A3BE8C">blackfire.agent_timeout=0.25</span></span>
<span class="line"></span></code></pre>
<p>Finally, that's everything. You're one deploy removed from profiling your production Vapor app! Check out the <a href="https://blackfire.io/docs/profiling-cookbooks/index">official docs on profiling your application</a> to get started doing some actual work.</p>
<h2>Conclusion</h2>
<p>Laravel Vapor could probably do with an official Blackfire layer and some documentation on how to configure it. Luckily for you we've already done some of the research to make your job easier. We can also make your job easier by reporting and solving errors in your Lambda app for you: <a href="https://flareapp.io/features">take a look at Flare's features</a>!</p>
]]>
            </summary>
                                    <updated>2020-12-17T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[How to safely delete records in massive tables on AWS using Laravel]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/how-to-safely-delete-records-in-massive-tables-on-aws-using-laravel" />
            <id>https://flareapp.io/how-to-safely-delete-records-in-massive-tables-on-aws-using-laravel</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When reviewing the contents of the database of <a href="https://flareapp.io">Flare</a>, we encountered a table with 1 billion records. Technically that isn't a problem. Flare runs on Vapor and uses an Aurora database, so it can handle that scale. But of course, there's a cost for storing that many records.</p>
<p>We dove in and concluded that we could safely delete about 900 million records. They all were created before a specific date.</p>
<h2>Attempt 1: running a single delete query</h2>
<p>A first naive approach would be to run a simple delete query.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">DELETE FROM table_with_too_many_records </span></span>
<span class="line"><span style="color: #D8DEE9FF">WHERE created_at </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2020 -01-01 00:00:00</span><span style="color: #ECEFF4">&#39;</span></span>
<span class="line"></span></code></pre>
<p>Performing a delete query like this on a small table will probably work. But when your table contains a billion items, it will likely cause problems.</p>
<p>In most cases, a database will try to perform all queries atomically, so no other queries see intermediary results. When a deletion query using a <code>where</code> clause starts, it will lock that table. All other queries that are run against the table will have to wait.</p>
<p>Because of the massive size of the table, the deletion query would probably run for hours. The lock would be in place for hours, essentially halting the entire app.</p>
<p>So, just running a single query to clean up the table isn't a good idea.</p>
<h2>Attempt 2: using a limit</h2>
<p>To make the query complete faster, you can add a <code>limit</code> clause. Because the query completes faster, the table lock will be released much faster as well. Other queries wont be halted as long.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">DELETE FROM table_with_too_many_records </span></span>
<span class="line"><span style="color: #D8DEE9FF">WHERE created_at </span><span style="color: #81A1C1">&lt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">2020 -01-01 00:00:00` </span></span>
<span class="line"><span style="color: #A3BE8C">LIMIT 1000;</span></span>
<span class="line"></span></code></pre>
<p>Here's an artisan command that performs that query.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Console</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Commands</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Domain</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Error</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Models</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ErrorOccurrence</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Console</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Command</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CleanTableCommand</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">Command</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">signature</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">clean-table</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">description</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Clean up the table</span><span style="color: #ECEFF4">&#39;</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Cleaning table...</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">MyModel</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">created_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">&lt;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">subMonth</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">limit</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1000</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">delete</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">All done!</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>In the code above, only the 1000 first selected are deleted. Our tables contain many more records that should be deleted.</p>
<p>A deletion query in Laravel returns the number of deleted records. We can use that to determine whether if it's worthwhile to rerun the deletion query.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">Cleaning table...</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">do</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">numberOfRecordsDeleted</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MyModel</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">created_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">&lt;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">subMonth</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">	        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">limit</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1000</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">	        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">delete</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">	    </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">comment</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Deleted </span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">numberOfRecordsDeleted</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C"> records</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">while</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">numberOfRecordsDeleted</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$this-&gt;</span><span style="color: #88C0D0">info</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">All done!</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This strategy works quite well on traditional systems where there's no limit on how long a process can run. If you use a conventional server instead of AWS Lambda and want to use the cleanup strategy above, look at <a href="https://github.com/spatie/laravel-model-cleanup">our model cleanup package</a>.</p>
<p>The problem with this strategy on AWS Lambda is that there is a hard execution time limit of 15 minutes. When cleaning up a huge table, the process would likely take multiple hours, so doing the cleanup in a single artisan command isn't possible.</p>
<h2>Attempt 3: using jobs</h2>
<p>To overcome that execution time limit of 15 minutes, the deletion logic can be moved to a job. The job can dispatch itself to delete more records.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Jobs</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Bus</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Queueable</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Queue</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">SerializesModels</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Queue</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">InteractsWithQueue</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Contracts</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Queue</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ShouldQueue</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Foundation</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Bus</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Dispatchable</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CleanTableJob</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">implements</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ShouldQueue</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Dispatchable</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">InteractsWithQueue</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Queueable</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">SerializesModels</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">numberOfRecordsDeleted</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MyModel</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">created_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">&lt;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">subMonth</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">limit</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1000</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">delete</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">numberOfRecordsDeleted</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self::</span><span style="color: #88C0D0">dispatch</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">static::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This strategy works great, the query executes fast, and the deletion process can keep running as long as needed.</p>
<h2>Scheduling the cleanup process</h2>
<p>In Laravel's console kernel, you can, in addition to artisan commands, schedule jobs.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// in app/Console/Kernel.php</span></span>
<span class="line"><span style="color: #81A1C1">protected</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">schedule</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">Schedule</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">schedule</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">   </span><span style="color: #616E88">// other scheduled tasks</span></span>
<span class="line"><span style="color: #D8DEE9FF">   </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">schedule</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">job</span><span style="color: #ECEFF4">(\</span><span style="color: #D8DEE9FF">App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Jobs</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">CleanTableJob</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">hourly</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>If <code>CleanTableJob</code> implements <code>ShouldQueue</code> (and in our case it does), the scheduler will dispatch the job on the queue, and it will not stall next entries in the schedule.</p>
<h2>Preventing overlaps</h2>
<p>There is one more caveat. In the example above, the <code>CleanTableJob</code> is scheduled to be dispatched hourly. The job will re-dispatch itself if more records need to be deleted.</p>
<p>Now imagine that the <code>CleanTableJob</code> will re-dispatch itself for more than an hour. While there is still a <code>CleanTableJob</code> running, the <code>CleanTableCommand</code> will dispatch another <code>CleanTableJob</code>. That will cause multiple <code>CleanTableJob</code> jobs to run at the same time.</p>
<p>In our specific situation, where we are cleaning up a table with hundreds of millions of records, it likely takes hours or even days to clean up the table. If left unchecked, there would be a great many <code>CleanTableJob</code> running. Ideally, we want one <code>CleanTableJob</code> running at any given time.</p>
<p>We can solve this problem by using an atomic lock. Laravel has support for this <a href="https://laravel.com/docs/master/cache#atomic-locks">out of the box</a>. Here's how using the lock could look like.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// in `CleanTableJob`</span></span>
<span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">lock</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Cache</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">store</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">redis</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">lock</span><span style="color: #ECEFF4">(</span><span style="color: #8FBCBB">DeleteOldErrorsJob</span><span style="color: #81A1C1">::class</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">_lock</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">60</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">		</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">lock</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// couldn&#39;t acquire lock, other job is probably running</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">numberOfRecordsDeleted</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MyModel</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">created_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">&lt;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">subMonth</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">limit</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1000</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">delete</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// release to lock, so a next job can get it</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">lock</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">release</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">numberOfRecordsDeleted</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">self::</span><span style="color: #88C0D0">dispatch</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">static::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>This code looks good, but it can be improved. That locking code makes it hard to see the essence of the job, which is deleting rows.</p>
<p>The locking code can be moved to <a href="https://laravel.com/docs/8.x/queues#job-middleware">a job middleware</a>.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Jobs</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Middleware</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Facades</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Cache</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">AtomicJobMiddleware</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * Process the queued job.</span></span>
<span class="line"><span style="color: #616E88">     *</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88">  </span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Contracts</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Queue</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Job</span><span style="color: #616E88">  $job</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@param</span><span style="color: #616E88">  </span><span style="color: #8FBCBB">callable</span><span style="color: #616E88">  $next</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">job</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">next</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #616E88">/** </span><span style="color: #81A1C1">@var</span><span style="color: #616E88"> </span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #616E88">Cache</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">RedisLock</span><span style="color: #616E88"> $lock */</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">lock</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Cache</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">store</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">redis</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">lock</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #81A1C1">{$</span><span style="color: #D8DEE9">job</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">resolveName</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">}</span><span style="color: #A3BE8C">_lock</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">10</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">60</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">!</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">lock</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">get</span><span style="color: #ECEFF4">())</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">job</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">delete</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">next</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">job</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">lock</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">release</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Here's the rewritten <code>CleanTableJob</code> that makes use of the <code>AtomicJobMiddleware</code>.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// in `CleanTableJob`</span></span>
<span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">numberOfRecordsDeleted</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">MyModel</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">query</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">where</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">created_at</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">&lt;</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">now</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">subMonth</span><span style="color: #ECEFF4">())</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">limit</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">1000</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">delete</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">numberOfRecordsDeleted</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">self::</span><span style="color: #88C0D0">dispatch</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">static::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">middleware</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span><span style="color: #81A1C1">new</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Jobs</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Middleware</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">AtomicJobMiddleware</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">]</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>And with all this in place, we now have a pretty reliable way of cleaning up big tables.</p>
<h2>In closing</h2>
<p>Cleaning big tables isn't that difficult, but many caveats need to be taken care of. I hope this post helps you in cleaning up your tables.</p>
<p>If you're using an Aurora cluster on AWS, you should be aware that deleting a lot of records won't make your bill smaller as space used by the deleted records <a href="https://aws.amazon.com/premiumsupport/knowledge-center/view-storage-aurora-cluster/">will still be allocated</a>.  Here's <a href="https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Aurora.Overview.StorageReliability.html#aurora-storage-data-billing">how to reclaim the allocated space</a>.</p>
<p>If you're not on AWS and want to clean up big tables in a safe way, take a look at <a href="https://github.com/spatie/laravel-model-cleanup">our model clean up package</a>.</p>
<p>We are currenlty optimizing <a href="https://flareapp.io">Flare</a> some more, and will soon release a first batch of new features. Stay tuned for that!</p>
]]>
            </summary>
                                    <updated>2020-09-21T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing JavaScript error tracking]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-javascript-error-tracking" />
            <id>https://flareapp.io/introducing-javascript-error-tracking</id>
            <author>
                <name><![CDATA[Sebastian]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>This week, we are bringing delightful JavaScript error tracking to Flare.</p>
<p>The integration comes with libraries for vanilla JavaScript, Vue, and React. You can upload your JavaScript source maps so that Flare can un-minify your compiled JavaScript files and point you directly to the error.</p>
<p>Watch the introduction video, where we showcase some of the features:</p>
<h2>Everything in one place</h2>
<p>Flare groups all your tracked Laravel and Javascript errors in one project and makes it easy to understand what is going wrong. If you have a growing team and want to send notifications to frontend or backend developers separately, create additional projects in your Flare account. All plans come with an unlimited amount of projects.</p>
<p><img src="https://beyondco.de/flare/js_1.png" alt="JavaScript Error Tracking Screenshot" /></p>
<p>With this release, Flare supports error tracking beyond Laravel and plain PHP applications and makes it easy for you to monitor your full stack from backend to frontend code.</p>
<p>If you are not sure if Flare is the right fit for you, start a free trial and see how things are going.</p>
]]>
            </summary>
                                    <updated>2020-01-28T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Track Wordpress errors with Flare]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/track-wordpress-errors-with-flare" />
            <id>https://flareapp.io/track-wordpress-errors-with-flare</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Sometimes, you don't have Laravel projects only but also run a classic Wordpress blog. Do you want to monitor this blog with Flare, too? We got you covered!</p>
<p>Wordpress doesn't use composer out-of-the-box, but there are many guides on how you can do that. For this article, we go with the most straightforward way and composer default. It might not be the best option to add composer to your Wordpress setup, but if you are already using Wordpress and composer, you don't have this issue anyway.</p>
<p>Flare itself has been designed for Laravel projects and so has <a href="https://flareapp.io/ignition">Ignition</a>. Ignition depends on many Laravel internals and if you want to use Flare with Wordpress, you can use the <a href="https://flareapp.io/docs/flare-for-generic-php-projects/installation">Generic PHP SDK</a> that powers Ignition.</p>
<h2>Install the Generic PHP SDK for Flare</h2>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">composer </span><span style="color: #81A1C1">require</span><span style="color: #D8DEE9FF"> facade</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">flare</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">client</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">php</span></span>
<span class="line"></span></code></pre>
<p>If you run this command in the root directory of your Wordpress project, this creates a <code>composer.json</code> file and downloads the SDK. Before we can register the Flare error handlers, we need to make sure Wordpress loads the packages that composer manages. Open the <code>wp-config.php</code> file that is unique per installation and does not get updated if a new version of Wordpress is available. The file lives in your root folder.</p>
<p>Add the following two lines to the top of your Wordpress config file to load composer dependencies and register the Flare error handlers. Make sure to add your own key there.</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">require_once</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">__DIR__</span><span style="color: #81A1C1">.</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">/vendor/autoload.php</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">Facade</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">FlareClient</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">D2lRPxchfWMCYNyoWawQgSRuGtEhygpK</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">registerFlareHandlers</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Test your integration by changing the database connection to something that doesn't work and refresh your Wordpress blog. This should trigger an error that you receive in Flare.</p>
]]>
            </summary>
                                    <updated>2019-11-22T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Using Flare with Lumen]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/using-flare-with-lumen" />
            <id>https://flareapp.io/using-flare-with-lumen</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>The main goal of Flare is to be the best error tracker for Laravel applications. Since we started, many people are asking how they can use Flare with other PHP applications that they run and how this works. For this purpose, we released a plain PHP SDK that also powers Flares Laravel client <a href="https://flareapp.io/ignition">Ignition</a> under the hood.</p>
<p>In this article, we are using Flare to track errors in a <a href="https://lumen.laravel.com">Laravel Lumen</a> application.</p>
<p>At first, you need to create a new project at Flare to get your Flare application key and access the Flare API. You can do this on your project overview <a href="https://flareapp.io/projects">here</a>.</p>
<p>The second step is to install the Flare PHP client package via composer:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">composer </span><span style="color: #81A1C1">require</span><span style="color: #D8DEE9FF"> facade</span><span style="color: #81A1C1">/</span><span style="color: #D8DEE9FF">flare</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">client</span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF">php</span></span>
<span class="line"></span></code></pre>
<p>The next step is to make sure that you are loading the default <code>AppServiceProvider</code> in Lumen. The service provider is not loaded by default, but you can do that by uncommenting line <code>79</code>in the <code>bootstrap/app.php</code> in your Lumen project directory. It should look like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">app</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Providers</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">AppServiceProvider</span><span style="color: #81A1C1">::class</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>The last step is to send all errors from your Lumen application to Flare. This is quite simple and works by registering the Flare handlers within the <code>AppServiceProvider</code>. For a default Lumen install, it looks like this:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">&lt;?</span><span style="color: #D8DEE9FF">php</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">namespace</span><span style="color: #D8DEE9FF"> App</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Providers</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Facade</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">FlareClient</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #81A1C1">use</span><span style="color: #D8DEE9FF"> Illuminate</span><span style="color: #ECEFF4">\</span><span style="color: #D8DEE9FF">Support</span><span style="color: #ECEFF4">\</span><span style="color: #8FBCBB">ServiceProvider</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">AppServiceProvider</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">extends</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">ServiceProvider</span></span>
<span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #616E88">/**</span></span>
<span class="line"><span style="color: #616E88">     * Register any application services.</span></span>
<span class="line"><span style="color: #616E88">     *</span></span>
<span class="line"><span style="color: #616E88">     * </span><span style="color: #81A1C1">@return</span><span style="color: #616E88"> </span><span style="color: #81A1C1">void</span></span>
<span class="line"><span style="color: #616E88">     */</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">public</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">function</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #8FBCBB">Flare</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">register</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">ZHb7j15RDQ1adLHjXSNWdsinSu1dhyMr</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">-&gt;</span><span style="color: #88C0D0">registerFlareHandlers</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre>
<p>Make sure you use your own <code>APP_KEY</code> and do not add the key directly in the file as this file is version controlled, and you don't want to have credentials in there. A better practice is to add this key to the <code>.env</code> file.</p>
]]>
            </summary>
                                    <updated>2019-11-22T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Creating business opportunities with error tracking]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/creating-business-opportunities-with-error-tracking" />
            <id>https://flareapp.io/creating-business-opportunities-with-error-tracking</id>
            <author>
                <name><![CDATA[Rias]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>When running production applications for your clients, it can take days until a bug is reported to your team. This reporting-delay might not be critical to the business of your client if it is a low traffic site, but it might be a direct financial loss if it’s a high-traffic application. If the site doesn’t have a high amount of traffic, it’s still a bad experience for the user, and it will have a negative impact on the business in the long-term–a negative impact that can be avoided easily.</p>
<p>This is the point where an error tracker comes into place. The error tracker takes care if something breaks, and you instantly get a notification. With this notification, you receive much additional information about the incident so that tracking down the issue becomes much more comfortable than relying on screenshots provided by a user who contacts the customer support of your client. With the error tracker, you stay in the loop and be aware of the problem before the client calls and raises the issue. In Flare, the notification also includes the user who got the error, other useful context information, and if it is a common issue–a solution how to fix it.</p>
<p>When working with clients, they usually require some service-level-agreement when an application goes live. The easiest way to handle this on a basic level is by adding a monitoring service like Flare to the application and dedicate some weekly time to handle errors. This formalizes responsibilities but also creates business opportunities along the way. While the SLA covers the costs for the error tracker multiple times, you create new touch-points with your clients. These touch-points can be used to offer new services or to stay connected with your contacts.</p>
<h2>This is how a happy client looks like</h2>
<p><img src="https://content.spatie.be/assets/flare/creating-business-opportunities-with-error-tracking/no-errors.png" alt="No Errors" /></p>
<p>Staying in touch via errors might sound like an odd way for a new opportunity. Still, if you can solve an issue before the client event notices the problem themselves and notify them that you’ve already handled it, they know that they are in good hands.</p>
<p>Being recognized as a partner that cares, usually brings in a steady stream for project requests and makes it easier for you to run your business.</p>
<p>With Flare, you can track unlimited projects, so if the first SLA already pays the bill, Flare is free for all other applications, and you can add your internal applications at no additional cost.</p>
]]>
            </summary>
                                    <updated>2019-11-18T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Sending logs to Flare]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/sending-logs-to-flare" />
            <id>https://flareapp.io/sending-logs-to-flare</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Flare is an error tracker for Laravel, but what do you do if you want notifications for other events which do not throw exceptions? You might wish to receive notifications for failing payments or other actions that users do in your application and that don't throw errors but where you want to know that they happen.</p>
<p>Since Ignition 1.9.0, Flare supports the collection of logs. You can check out the documentation for this feature <a href="https://flareapp.io/docs/ignition-for-laravel/sending-logs-to-flare">here</a>.</p>
<p>The default configuration for the log recording feature of Flare is the log level <code>error</code>, and all logs that happen as <em>Error</em>, <em>Emergency</em>, and <em>Critical</em> trigger a notification instantly. The level for this is configurable at the config file within your application:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #616E88">// in your config/logging.php</span></span>
<span class="line"><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">[</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">driver</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">flare</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">level</span><span style="color: #ECEFF4">&#39;</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">critical</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #ECEFF4">],</span></span>
<span class="line"></span></code></pre>
<p>The feature itself can be enabled and disabled in the same file with the key:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #D8DEE9FF">send_logs_as_events</span></span>
<span class="line"></span></code></pre>
<p>Make sure to check our documentation and learn how you get the most out of your Flare subscription.</p>
]]>
            </summary>
                                    <updated>2019-11-06T00:00:00+00:00</updated>
        </entry>
            <entry>
            <title><![CDATA[Introducing "ddd" - a new global helper for Laravel]]></title>
            <link rel="alternate" href="https://flareapp.io/blog/introducing-ddd-a-new-global-helper-for-laravel" />
            <id>https://flareapp.io/introducing-ddd-a-new-global-helper-for-laravel</id>
            <author>
                <name><![CDATA[Freek]]></name>
            </author>
            <summary type="html">
                <![CDATA[<p>Today we are very excited to announce a new global helper function that is available for all Laravel 6 installations, as well as for all applications that have <a href="/docs/integration/laravel-customizations/introduction">Ignition 1.9+ installed</a>.</p>
<p>We all love to debug our code using <code>dd</code> - it's fast and easy. But sometimes you want to have some more information available.<br />
Or you forgot to dump some things about your request, so you have to edit your dd and do it all over again.</p>
<h2>Say hello to &quot;ddd&quot;</h2>
<p>We now give you <code>ddd</code> - a globally available helper function that does everything that you love about <code>dd</code> and sprinkles everything that Ignition has to offer on top of it.</p>
<p>Let's say you want to dump a string and a user model somewhere in the code. With <code>ddd</code> you can do this, just as you would with <code>dd</code>:</p>
<pre class="shiki" style="background-color: #2e3440ff"><code><span class="line"><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">User</span><span style="color: #81A1C1">::</span><span style="color: #88C0D0">first</span><span style="color: #ECEFF4">()</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #88C0D0">ddd</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&#39;</span><span style="color: #A3BE8C">dumping this one</span><span style="color: #ECEFF4">&#39;</span><span style="color: #ECEFF4">,</span><span style="color: #88C0D0"> </span><span style="color: #81A1C1">$</span><span style="color: #D8DEE9">user</span><span style="color: #ECEFF4">)</span><span style="color: #81A1C1">;</span></span>
<span class="line"></span></code></pre>
<p>Now when you hit that code in your browser, the <code>ddd</code> helper will dump the data, show Ignition and exit. This gives you the power of <code>dd</code> combined with all the great features of Ignition. And if you ever feel like you need some help from other people, you can go and share your dumped code right from Ignition too.<br />
Here is an <a href="https://flareapp.io/share/NPLYpo7w#F50">example share</a> taken from our <code>ddd</code> test application.</p>
]]>
            </summary>
                                    <updated>2019-09-30T00:00:00+00:00</updated>
        </entry>
    </feed>
