<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hans</title><link>https://hansvl.nl/</link><description>Recent posts on Hans</description><language>en-uk</language><category>Software Development</category><image><url>https://hansvl.nl/apple-touch-icon.png</url><title>Home</title><link>https://hansvl.nl/</link></image><generator>Hugo -- gohugo.io</generator><managingEditor>contact@hansvl.nl (Hans van Luttikhuizen-Ross)</managingEditor><webMaster>contact@hansvl.nl (Hans van Luttikhuizen-Ross)</webMaster><atom:link href="https://hansvl.nl/rss.xml" rel="self" type="application/rss+xml"/><item><title>Fixing “Malformed UTF-8 characters” in Laravel</title><link>https://hansvl.nl/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/</link><pubDate>Wed, 10 Dec 2025 00:02:58 +0100</pubDate><author>Hans van Luttikhuizen-Ross</author><description><![CDATA[ <p><img align="left" hspace="5" style="margin-bottom: 1rem;" src="https://hansvl.nl/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/malformed-utf8-characters_cover_hu_5ba373b6b40ab10b.png"/></p><blockquote>
<p>Malformed UTF-8 characters, possibly incorrectly encoded</p>
</blockquote>
<p>If you have ever seen this exception, chances are that you are saving byte strings to the database
using a package like <a href="https://github.com/michaeldyrynda/laravel-model-uuid" title="laravel-model-uuid" target="_blank" rel="nofollow noopener"><code>laravel-model-uuid</code></a>.</p>
<p>A byte string is arbitrary byte data formatted as a string; bytes, with <code>string</code> as their vehicle.
This is great for efficiently storing small amounts of data, but as it’s arbitrary data, it’s not
valid <a href="https://en.wikipedia.org/wiki/UTF-8" title="UTF-8" target="_blank" rel="nofollow noopener">UTF-8</a>.</p>
<p>Problems arise when you try to display this data in a web browser or try to parse it somehow.</p>
<p>Normally speaking, you wouldn’t. So why do we see this Symfony exception page?</p>
<h2 id="cause" class="link-owner">
  Cause
</h2><p>Laravel has a beautiful exception page that displays a lot of useful information about the exception
that was thrown; the exception class and message, the stack trace, request and session details,
and—relevant to what we’re doing—all SQL queries that have occurred during that request up until the
point where the exception was thrown, along with their bindings<sup id="fnref:1"><a href="https://hansvl.nl/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>If the exception being rendered is a database exception, the relevant SQL query is also included in
the message.</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/laravel-exception-page.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/laravel-exception-page_hu_49e3d54147d3d13b.webp" />
          <source media="(max-width: 767px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/laravel-exception-page_hu_b2594e622b1a57a3.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/laravel-exception-page_hu_eec3ae760bf53e72.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/laravel-exception-page_hu_5a778cd867af3821.webp" />
          <img
            class="rounded-lg mx-auto"
            title="A screenshot of a Laravel exception page displaying a QueryException, complete with the relevant SQL query and bindings."
            alt="Note the SQL queries in the exception message and in the Queries section"
            loading="lazy"
            src="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/laravel-exception-page.png"
            
              style=" background: linear-gradient(-30deg, #292828,
              #9b1b44); background-size: cover; "
            
            width="2274"
            height="2496"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        Note the SQL queries in the exception message and in the Queries section
      </figcaption>
    </figure>
  


</p>
<p>But what happens if this page itself fails to render?</p>
<p>Whenever something goes wrong, an exception is thrown. Relevant data is collected and written to a
log, sent to an error reporting service, a webhook for your chat service is triggered, whatever you
have configured <code>config/logging.php</code>. And the exception page is rendered.</p>
<p>If—somewhere in the request lifecycle—a byte string is involved in a database operation, this will
also be included in the data being passed around, and that throws a spanner in the works.</p>
<p>At some point, the SQL query and its bindings will be parsed as JSON, and that function will fail,
because this byte string is—indeed—not valid UTF-8. Now a new exception is thrown. That exception
will be rendered as a Symfony exception page; a fallback for when the original exception page fails
to render. And the original exception will be lost.</p>
<h2 id="solution" class="link-owner">
  Solution
</h2><p>To solve this issue, we need to turn the byte strings into something that is actually valid UTF-8
before it is passed to something that expects valid UTF-8. There are three problems we need to
solve:</p>
<ul>
<li>The bindings in a <code>QueryException</code>’s message need to be sanitized</li>
<li>The bindings in the Queries section of the exception page need to be sanitized</li>
<li>A bonus problem that we’ll tackle later</li>
</ul>
<p>Let’s dive in.</p>
<h3 id="query-exceptions" class="link-owner">
  Query exceptions
</h3><p>A <code>Illuminate\Database\QueryException</code> is thrown whenever a database operation fails. The message
contains the SQL query and its bindings. We can trigger one by trying to create a user where a
non-nullable field isn’t specified.</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="c1">// No email specified
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$user</span><span class="o">-&gt;</span><span class="na">create</span><span class="p">([</span><span class="s1">&#39;name&#39;</span> <span class="o">=&gt;</span> <span class="nx">hex2bin</span><span class="p">(</span><span class="s1">&#39;deadbeef&#39;</span><span class="p">)]);</span></span></span></code></pre>
  </div>
  


<p>We will go right to the source, the place where the exceptions are thrown, and the data is passed to
them: <code>Illuminate\Database\Connection::runQueryCallback</code>. All this method does is run the query
callback, and if an exception is thrown, it will be re-thrown as a <code>QueryException</code>.</p>
<p>If this happens, we can sanitize the bindings before re-throwing the exception:</p>

  

  
  
  
    
  
  
    
  

  <div class="highlight has-line-highlights highlight-wide" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">namespace</span> <span class="nx">App\Database</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">use</span> <span class="nx">Closure</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">use</span> <span class="nx">Exception</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Database\MySqlConnection</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Database\QueryException</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Database\UniqueConstraintViolationException</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">class</span> <span class="nc">ErrorSanitizingMySqlConnection</span> <span class="k">extends</span> <span class="nx">MySqlConnection</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">protected</span> <span class="k">function</span> <span class="nf">runQueryCallback</span><span class="p">(</span><span class="nv">$query</span><span class="p">,</span> <span class="nv">$bindings</span><span class="p">,</span> <span class="nx">Closure</span> <span class="nv">$callback</span><span class="p">)</span><span class="o">:</span> <span class="nx">mixed</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="k">return</span> <span class="nv">$callback</span><span class="p">(</span><span class="nv">$query</span><span class="p">,</span> <span class="nv">$bindings</span><span class="p">);</span>
</span></span><span class="line hl"><span class="ln">15</span><span class="cl">        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">Exception</span> <span class="nv">$e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="ln">16</span><span class="cl">            <span class="c1">// Sanitize bindings before creating the exception
</span></span></span><span class="line hl"><span class="ln">17</span><span class="cl"><span class="c1"></span>            <span class="nv">$sanitizedBindings</span> <span class="o">=</span> <span class="nx">sanitize_bindings</span><span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">prepareBindings</span><span class="p">(</span><span class="nv">$bindings</span><span class="p">));</span>
</span></span><span class="line hl"><span class="ln">18</span><span class="cl">
</span></span><span class="line hl"><span class="ln">19</span><span class="cl">            <span class="c1">// […]
</span></span></span><span class="line hl"><span class="ln">20</span><span class="cl"><span class="c1"></span>
</span></span><span class="line hl"><span class="ln">21</span><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="nx">QueryException</span><span class="p">(</span>
</span></span><span class="line hl"><span class="ln">22</span><span class="cl">                <span class="nx">connectionName</span><span class="o">:</span> <span class="p">(</span><span class="nx">string</span><span class="p">)</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">getName</span><span class="p">(),</span>
</span></span><span class="line hl"><span class="ln">23</span><span class="cl">                <span class="nx">sql</span><span class="o">:</span> <span class="nv">$query</span><span class="p">,</span>
</span></span><span class="line hl"><span class="ln">24</span><span class="cl">                <span class="nx">bindings</span><span class="o">:</span> <span class="nv">$sanitizedBindings</span><span class="p">,</span>
</span></span><span class="line hl"><span class="ln">25</span><span class="cl">                <span class="nx">previous</span><span class="o">:</span> <span class="nv">$e</span>
</span></span><span class="line hl"><span class="ln">26</span><span class="cl">            <span class="p">);</span>
</span></span><span class="line hl"><span class="ln">27</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  
    
    
      
    
    <div class="highlight-caption highlight-caption-wide">The method is functionally identical, except for passing sanitized bindings to the QueryException</div>
  


<p>We can’t override <code>Illuminate\Database\Connection::prepareBindings</code> (line 17), as that would
sanitize ALL bindings, including those that would successfully be inserted into the database.</p>
<p>As <code>runQueryCallback</code> is defined on <code>Illuminate\Database\Connection</code>, without any ways to hook into
it, we need to override the class for the database connection we are using, e.g.,
<code>Illuminate\Database\MySqlConnection</code>.</p>
<p>We sanitize the bindings with a helper that we will reuse later:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">sanitize_bindings</span><span class="p">(</span><span class="k">array</span> <span class="nv">$bindings</span><span class="p">)</span><span class="o">:</span> <span class="k">array</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">array_map</span><span class="p">(</span><span class="k">function</span> <span class="p">(</span><span class="nv">$binding</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nx">is_string</span><span class="p">(</span><span class="nv">$binding</span><span class="p">)</span> <span class="o">||</span> <span class="nx">mb_check_encoding</span><span class="p">(</span><span class="nv">$binding</span><span class="p">,</span> <span class="s1">&#39;UTF-8&#39;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nv">$binding</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s1">&#39;0x&#39;</span><span class="o">.</span><span class="nx">bin2hex</span><span class="p">(</span><span class="nv">$binding</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span> <span class="nv">$bindings</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  
    
    
    <div class="highlight-caption">Each non-UTF-8 string binding is replaced with its hexadecimal representation</div>
  


<p>Now we need to make Laravel use our custom connection class.</p>
<p>Laravel is built with dependency injection as a core part of its architecture. This means that we
can swap out nearly any functionality the framework offers for our own implementation, including the
class used to manage database connections. We can do this by adding the following to
<code>App\Providers\AppServiceProvider::boot</code>:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="nx">Connection</span><span class="o">::</span><span class="na">resolverFor</span><span class="p">(</span><span class="s1">&#39;mysql&#39;</span><span class="p">,</span> <span class="k">function</span> <span class="p">(</span><span class="nv">$connection</span><span class="p">,</span> <span class="nv">$database</span><span class="p">,</span> <span class="nv">$prefix</span><span class="p">,</span> <span class="nv">$config</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">new</span> <span class="nx">ErrorSanitizingMySqlConnection</span><span class="p">(</span><span class="nv">$connection</span><span class="p">,</span> <span class="nv">$database</span><span class="p">,</span> <span class="nv">$prefix</span><span class="p">,</span> <span class="nv">$config</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre>
  </div>
  
    
    
    <div class="highlight-caption">Registering the custom connection using dependency injection</div>
  


<p>And with these changes
(<a href="https://github.com/pindab0ter/malformed-utf8-demo/commit/ab93e2e653f1d1b0a656bd123026dcd68b2d9abe" title="source" target="_blank" rel="nofollow noopener">source</a>):
Success! The exception renders correctly:</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-exception-message.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-exception-message_hu_b12b5718daa134cb.webp" />
          <source media="(max-width: 767px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-exception-message_hu_75f61b19dfcae4fb.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-exception-message_hu_f35f921a68a6786.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-exception-message_hu_2c5ea86adcf06060.webp" />
          <img
            class="rounded-lg mx-auto"
            title="A screenshot of a Laravel exception page displaying a QueryException, complete with the relevant SQL query and the sanitized bindings displaying correctly."
            alt="Note the sanitized SQL query and bindings in the exception message"
            loading="lazy"
            src="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-exception-message.png"
            
              style=" background: linear-gradient(-30deg, #2e2e2e,
              #b5b3b3); background-size: cover; "
            
            width="2404"
            height="730"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        Note the sanitized SQL query and bindings in the exception message
      </figcaption>
    </figure>
  


</p>
<hr>
<h3 id="the-queries-section" class="link-owner">
  The Queries section
</h3><p>A <code>QueryException</code>’s message is not the only place where the SQL query and its bindings are
displayed. The Queries section of the exception page also displays them. So something like this will
still trigger the “Malformed UTF-8 characters” error:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="nx">User</span><span class="o">::</span><span class="na">find</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">-&gt;</span><span class="na">update</span><span class="p">([</span><span class="s1">&#39;name&#39;</span> <span class="o">=&gt;</span> <span class="nx">hex2bin</span><span class="p">(</span><span class="s1">&#39;deadbeef&#39;</span><span class="p">)]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">throw</span> <span class="k">new</span> <span class="nx">Exception</span><span class="p">(</span><span class="s1">&#39;Whoops!&#39;</span><span class="p">);</span></span></span></code></pre>
  </div>
  
    
    
    <div class="highlight-caption">Successfully executed query will end up in the Queries section of the exception page</div>
  


<p>To solve this, we need to override the method responsible for rendering the exception. Or to be more
precise, the method that generates the data for the Queries section.</p>

  

  
  
  
    
  
  
    
  

  <div class="highlight has-line-highlights highlight-wide" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">namespace</span> <span class="nx">App\Exceptions\Renderer</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Foundation\Exceptions\Renderer\Exception</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">class</span> <span class="nc">ConfigurableFrameException</span> <span class="k">extends</span> <span class="nx">Exception</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">applicationQueries</span><span class="p">()</span><span class="o">:</span> <span class="k">array</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="nv">$queries</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">listener</span><span class="o">-&gt;</span><span class="na">queries</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span> <span class="nx">array_map</span><span class="p">(</span><span class="k">function</span> <span class="p">(</span><span class="k">array</span> <span class="nv">$query</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="ln">12</span><span class="cl">            <span class="nv">$sql</span> <span class="o">=</span> <span class="p">(</span><span class="nx">string</span><span class="p">)</span> <span class="nv">$query</span><span class="p">[</span><span class="s1">&#39;sql&#39;</span><span class="p">];</span>
</span></span><span class="line hl"><span class="ln">13</span><span class="cl">
</span></span><span class="line hl"><span class="ln">14</span><span class="cl">            <span class="nv">$sanitizedBindings</span> <span class="o">=</span> <span class="nx">sanitize_bindings</span><span class="p">(</span><span class="nv">$query</span><span class="p">[</span><span class="s1">&#39;bindings&#39;</span><span class="p">]);</span>
</span></span><span class="line hl"><span class="ln">15</span><span class="cl">
</span></span><span class="line hl"><span class="ln">16</span><span class="cl">            <span class="k">foreach</span> <span class="p">(</span><span class="nv">$sanitizedBindings</span> <span class="k">as</span> <span class="nv">$binding</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="ln">17</span><span class="cl">                <span class="nv">$result</span> <span class="o">=</span> <span class="nx">match</span> <span class="p">(</span><span class="nx">gettype</span><span class="p">(</span><span class="nv">$binding</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="ln">18</span><span class="cl">                    <span class="s1">&#39;integer&#39;</span><span class="p">,</span> <span class="s1">&#39;double&#39;</span> <span class="o">=&gt;</span> <span class="nx">preg_replace</span><span class="p">(</span><span class="s1">&#39;/\?/&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">string</span><span class="p">)</span> <span class="nv">$binding</span><span class="p">,</span> <span class="nv">$sql</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
</span></span><span class="line hl"><span class="ln">19</span><span class="cl">                    <span class="s1">&#39;NULL&#39;</span> <span class="o">=&gt;</span> <span class="nx">preg_replace</span><span class="p">(</span><span class="s1">&#39;/\?/&#39;</span><span class="p">,</span> <span class="s1">&#39;NULL&#39;</span><span class="p">,</span> <span class="nv">$sql</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
</span></span><span class="line hl"><span class="ln">20</span><span class="cl">                    <span class="k">default</span> <span class="o">=&gt;</span> <span class="nx">preg_replace</span><span class="p">(</span><span class="s1">&#39;/\?/&#39;</span><span class="p">,</span> <span class="s2">&#34;&#39;</span><span class="si">{</span><span class="nv">$binding</span><span class="si">}</span><span class="s2">&#39;&#34;</span><span class="p">,</span> <span class="nv">$sql</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
</span></span><span class="line hl"><span class="ln">21</span><span class="cl">                <span class="p">};</span>
</span></span><span class="line hl"><span class="ln">22</span><span class="cl">
</span></span><span class="line hl"><span class="ln">23</span><span class="cl">                <span class="nv">$sql</span> <span class="o">=</span> <span class="p">(</span><span class="nx">string</span><span class="p">)</span> <span class="nv">$result</span><span class="p">;</span>
</span></span><span class="line hl"><span class="ln">24</span><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">                <span class="s1">&#39;connectionName&#39;</span> <span class="o">=&gt;</span> <span class="nv">$query</span><span class="p">[</span><span class="s1">&#39;connectionName&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">                <span class="s1">&#39;time&#39;</span> <span class="o">=&gt;</span> <span class="nv">$query</span><span class="p">[</span><span class="s1">&#39;time&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">                <span class="s1">&#39;sql&#39;</span> <span class="o">=&gt;</span> <span class="nv">$sql</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">            <span class="p">];</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">        <span class="p">},</span> <span class="nv">$queries</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  
    
    
      
    
    <div class="highlight-caption highlight-caption-wide">The bindings are sanitized for every query caught by the listener</div>
  


<p>This whole method is an exact copy of the parent method, except for the sanitized bindings
(<a href="https://github.com/pindab0ter/malformed-utf8-demo/commit/ab93e2e653f1d1b0a656bd123026dcd68b2d9abe" title="source" target="_blank" rel="nofollow noopener">source</a>).</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-queries-section.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-queries-section_hu_737b4cdb9608081a.webp" />
          <source media="(max-width: 767px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-queries-section_hu_bf5f4b17d7697290.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-queries-section_hu_8f60460ada1beca7.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-queries-section_hu_3a5bfd87d29c9556.webp" />
          <img
            class="rounded-lg mx-auto"
            title="A screenshot of the Queries section of a Laravel exception page displaying the sanitized bindings."
            alt="Note the sanitized bindings in the Queries section"
            loading="lazy"
            src="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/sanitized-queries-section.png"
            
              style=" background-color: #2a2929; background-size: cover; "
            
            width="2274"
            height="476"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        Note the sanitized bindings in the Queries section
      </figcaption>
    </figure>
  


</p>
<p>With that, all places where the SQL query and its bindings are displayed are sanitized and will not
fail with a Malformed UTF-8 characters error anymore.</p>
<hr>
<h3 id="bonus-problem-stacks" class="link-owner">
  Bonus problem: Stacks
</h3><p>In fixing one problem, we have created another:</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/stack-trace-with-custom-connection.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/stack-trace-with-custom-connection_hu_2f5b9e4e8ed71653.webp" />
          <source media="(max-width: 767px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/stack-trace-with-custom-connection_hu_1409b7130ec607aa.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/stack-trace-with-custom-connection_hu_7abf66f91e53b20f.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/stack-trace-with-custom-connection_hu_75e885937dbbde8e.webp" />
          <img
            class="rounded-lg mx-auto"
            title="A screenshot of an exception page’s stack trace section displaying the custom Connection class
as the main culprit."
            alt="This is not where the error originated, but it is the first non-vendor class in the trace."
            loading="lazy"
            src="/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/stack-trace-with-custom-connection.png"
            
              style=" background: linear-gradient(-30deg, #2a2929,
              #8a1840); background-size: cover; "
            
            width="2404"
            height="1734"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        This is not where the error originated, but it is the first non-vendor class in the trace.
      </figcaption>
    </figure>
  


</p>
<p>Because we have implemented our own exception renderer, the stack trace will now show our custom
<code>Connection</code> class as the first non-vendor class in the trace. While correct, it is not very
helpful, as it masks the place where the original error occurred.</p>
<p>We can fix this, but it is a little more involved. The process is as follows:</p>
<ul>
<li>
<p>When an exception is thrown, <code>Illuminate\Foundation\Exceptions\Renderer\Renderer</code> takes a request
and a throwable and prepares the data to render the exception page: an instance of
<code>Illuminate\Foundation\Exceptions\Renderer\Exception</code></p>
</li>
<li>
<p>It takes the stack trace frames and turn them into instances of
<code>Illuminate\Foundation\Exceptions\Renderer\Frame</code></p>
</li>
<li>
<details>
  <summary>These frames are grouped based on whether they are ‘vendor frames’ or not</summary>
  
  

  
  
  
    
  
  
    
  

  <div class="highlight has-line-highlights highlight-wide" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="sd">/**
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="sd"> * Get the exception&#39;s frames grouped by vendor status.
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="sd"> *
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="sd"> * @return array&lt;int, array{is_vendor: bool, frames: array&lt;int, Frame&gt;}&gt;
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sd"> */</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="k">public</span> <span class="k">function</span> <span class="nf">frameGroups</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="nv">$groups</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">frames</span><span class="p">()</span> <span class="k">as</span> <span class="nv">$frame</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="ln">11</span><span class="cl">        <span class="nv">$isVendor</span> <span class="o">=</span> <span class="nv">$frame</span><span class="o">-&gt;</span><span class="na">isFromVendor</span><span class="p">();</span>
</span></span><span class="line hl"><span class="ln">12</span><span class="cl">
</span></span><span class="line hl"><span class="ln">13</span><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$groups</span><span class="p">)</span> <span class="o">||</span> <span class="nv">$groups</span><span class="p">[</span><span class="nx">array_key_last</span><span class="p">(</span><span class="nv">$groups</span><span class="p">)][</span><span class="s1">&#39;is_vendor&#39;</span><span class="p">]</span> <span class="o">!==</span> <span class="nv">$isVendor</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="ln">14</span><span class="cl">            <span class="nv">$groups</span><span class="p">[]</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line hl"><span class="ln">15</span><span class="cl">                <span class="s1">&#39;is_vendor&#39;</span> <span class="o">=&gt;</span> <span class="nv">$isVendor</span><span class="p">,</span>
</span></span><span class="line hl"><span class="ln">16</span><span class="cl">                <span class="s1">&#39;frames&#39;</span> <span class="o">=&gt;</span> <span class="p">[],</span>
</span></span><span class="line hl"><span class="ln">17</span><span class="cl">            <span class="p">];</span>
</span></span><span class="line hl"><span class="ln">18</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line hl"><span class="ln">19</span><span class="cl">
</span></span><span class="line hl"><span class="ln">20</span><span class="cl">        <span class="nv">$groups</span><span class="p">[</span><span class="nx">array_key_last</span><span class="p">(</span><span class="nv">$groups</span><span class="p">)][</span><span class="s1">&#39;frames&#39;</span><span class="p">][]</span> <span class="o">=</span> <span class="nv">$frame</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="k">return</span> <span class="nv">$groups</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  
    
    
      
    
    <div class="highlight-caption highlight-caption-wide">Frames are grouped by whether they are considered a vendor frame</div>
  



</details>

</li>
<li>
<p>Then finally, the <code>x-laravel-exceptions-renderer::trace</code> view component will render the grouped
frames.</p>
</li>
</ul>
<p>So the best way to fix this is to tackle the problem at the source: <em>Which classes are considered
vendor classes?</em></p>
<p>To do this, we need to override the <code>isFromVendor</code> method of the <code>Frame</code> class, we need to override
the <code>frames</code> method of the <code>Exception</code> class to use the custom <code>Frame</code>, and we need to override the
<code>render</code> method of the <code>Renderer</code> class to use our custom frames. Feel free to inspect the code
changes here:
<a href="https://github.com/pindab0ter/malformed-utf8-demo/commit/200b05d9c04f476d21d90fbf1f701022ffade1a8" title="Treat custom SQL connection as vendor class" target="_blank" rel="nofollow noopener">Treat custom SQL connection as vendor class</a></p>

  

  
  
  
    
  
  
    
  

  <div class="highlight has-line-highlights highlight-wide" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ConfigurableFrame</span> <span class="k">extends</span> <span class="nx">BaseFrame</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">isFromVendor</span><span class="p">()</span><span class="o">:</span> <span class="nx">bool</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="o">!</span> <span class="nx">str_starts_with</span><span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">frame</span><span class="p">[</span><span class="s1">&#39;file&#39;</span><span class="p">],</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">basePath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="o">||</span> <span class="nx">str_starts_with</span><span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">frame</span><span class="p">[</span><span class="s1">&#39;file&#39;</span><span class="p">],</span> <span class="nx">join_paths</span><span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">basePath</span><span class="p">,</span> <span class="s1">&#39;vendor&#39;</span><span class="p">))</span>
</span></span><span class="line hl"><span class="cl">            <span class="o">||</span> <span class="nx">array_any</span><span class="p">(</span>
</span></span><span class="line hl"><span class="cl">                <span class="nx">config</span><span class="p">(</span><span class="s1">&#39;app.classes_treated_as_from_vendor&#39;</span><span class="p">,</span> <span class="p">[]),</span>
</span></span><span class="line hl"><span class="cl">                <span class="nx">fn</span> <span class="p">(</span><span class="nv">$ignored</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">class</span><span class="p">()</span> <span class="o">===</span> <span class="nv">$ignored</span> <span class="o">||</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">frame</span><span class="p">[</span><span class="s1">&#39;class&#39;</span><span class="p">]</span> <span class="o">??</span> <span class="k">null</span><span class="p">)</span> <span class="o">===</span> <span class="nv">$ignored</span>
</span></span><span class="line hl"><span class="cl">            <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  
    
    
      
    
    <div class="highlight-caption highlight-caption-wide">The method responsible for determining whether a frame is from a vendor class</div>
  


<p>The repository is available on GitHub: <a href="https://github.com/pindab0ter/malformed-utf8-demo" title="pindab0ter/malformed-utf8-demo" target="_blank" rel="nofollow noopener">pindab0ter/malformed-utf8-demo</a></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Bindings are the values that are bound to the SQL query. Instead of the parameter placeholder
<code>?</code>, the actual value will be displayed. For example:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-sql not-prose" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- With parameter placeholder:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="s2">&#34;users&#34;</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="s2">&#34;users&#34;</span><span class="p">.</span><span class="s2">&#34;email&#34;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- With bindings:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="s2">&#34;users&#34;</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="s2">&#34;users&#34;</span><span class="p">.</span><span class="s2">&#34;email&#34;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&#34;some.user@example.com&#34;</span></span></span></code></pre>
  </div>
  


&#160;<a href="https://hansvl.nl/blog/2025-12-10-fixing-malformed-utf-8-characters-exceptions-in-laravel/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></li>
</ol>
</div>

              ]]></description></item><item><title>Constraining Laravel's MorphTo relation</title><link>https://hansvl.nl/blog/constraining-laravels-morph-to-relation/</link><pubDate>Tue, 20 May 2025 15:45:43 +0200</pubDate><lastBuildDate>Mon, 15 Dec 2025 13:43:43 +0100</lastBuildDate><author>Hans van Luttikhuizen-Ross</author><description><![CDATA[ <p><img align="left" hspace="5" style="margin-bottom: 1rem;" src="https://hansvl.nl/blog/constraining-laravels-morph-to-relation/constrained-morph-to-example_hu_fbf7cabc0785a7b6.png"/></p><blockquote>
<p><strong>Update:</strong> This is now available as a package on Packagist:
<a href="https://packagist.org/packages/pindab0ter/constrained-morph-to-for-laravel" title="pindab0ter/constrained-morph-to-for-laravel" target="_blank" rel="nofollow noopener"><code>pindab0ter/constrained-morph-to-for-laravel</code></a>,
source on <a href="https://github.com/pindab0ter/constrained-morph-to-for-laravel" title="GitHub" target="_blank" rel="nofollow noopener">GitHub</a>.</p>
</blockquote>
<p>In a project with a polymorphic one-to-one <code>MorphTo</code> relationship, you might want to constrain the
relationship to a specific model type. Say,
<a href="https://laravel.com/docs/12.x/eloquent-relationships#one-to-one-polymorphic-relations" title="for example" target="_blank" rel="nofollow noopener">for example</a>,
that you have a <code>Comment</code> model that can belong to either a <code>Post</code> or a <code>Video</code>; <code>commentable</code>s.</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-text not-prose" data-lang="text"><span class="line"><span class="cl">posts
</span></span><span class="line"><span class="cl">    id - integer
</span></span><span class="line"><span class="cl">    title - string
</span></span><span class="line"><span class="cl">    body - text
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">videos
</span></span><span class="line"><span class="cl">    id - integer
</span></span><span class="line"><span class="cl">    title - string
</span></span><span class="line"><span class="cl">    url - string
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">comments
</span></span><span class="line"><span class="cl">    id - integer
</span></span><span class="line"><span class="cl">    body - text
</span></span><span class="line"><span class="cl">    commentable_id - integer
</span></span><span class="line"><span class="cl">    commentable_type - string</span></span></code></pre>
  </div>
  


<p>Now we’re interested in a <code>Comment</code>’s <code>commentable</code>, but only if it’s a <code>Video</code>.</p>
<p>One way you could do this is to load the relation and then check if the <code>commentable</code> is an instance
of <code>Video</code>:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="nv">$commentable</span> <span class="o">=</span> <span class="nx">Comment</span><span class="o">::</span><span class="na">find</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">-&gt;</span><span class="na">commentable</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nv">$commentable</span> <span class="nx">instanceof</span> <span class="nx">Video</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Do something with the video
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span></span></span></code></pre>
  </div>
  


<p>This works, but you would have to do this in every single place where you want this behaviour. It
also loads the relation even if it’s not the one you want.</p>
<p>It would be much better if you could just modify the relation so that it returns only the specific
type of model you want.</p>
<p>You can do kind of do this by defining a new relationship and adding a <code>whereHas</code> referring back to
the declaring model like so<sup id="fnref:1"><a href="https://hansvl.nl/blog/constraining-laravels-morph-to-relation/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Comment</span> <span class="k">extends</span> <span class="nx">Model</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">commentable</span><span class="p">()</span><span class="o">:</span> <span class="nx">MorphTo</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">morphTo</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">video</span><span class="p">()</span><span class="o">:</span> <span class="nx">MorphTo</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$this</span>
</span></span><span class="line"><span class="cl">          <span class="o">-&gt;</span><span class="na">commentable</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">          <span class="o">-&gt;</span><span class="na">whereHas</span><span class="p">(</span><span class="s1">&#39;comments&#39;</span><span class="p">,</span> <span class="k">function</span> <span class="p">(</span><span class="nx">Builder</span> <span class="nv">$query</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="nv">$query</span><span class="o">-&gt;</span><span class="na">where</span><span class="p">(</span><span class="s1">&#39;comments.commentable_type&#39;</span><span class="p">,</span> <span class="nx">Video</span><span class="o">::</span><span class="na">class</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">          <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  


<p>This solution only works if every class that is saved as a <code>commentable</code> has a relation back to the
<code>Comment</code> model. Another downside is that it still loads the related model even if it is not of the
type you want.</p>
<h2 id="looking-for-a-solution" class="link-owner">
  Looking for a solution
</h2><p>When you try to simply add a <code>where</code> to the <code>MorphTo</code> relation, you will see that it doesn’t quite
work as you might expect.</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="nv">$comment</span><span class="o">-&gt;</span><span class="na">commentable</span><span class="p">()</span><span class="o">-&gt;</span><span class="na">where</span><span class="p">(</span><span class="s1">&#39;commentable_type&#39;</span><span class="p">,</span> <span class="nx">Video</span><span class="o">::</span><span class="na">class</span><span class="p">)</span><span class="o">-&gt;</span><span class="na">toSql</span><span class="p">();</span></span></span></code></pre>
  </div>
  


<p>This returns the following SQL (formatted for readability):</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-sql not-prose" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="o">`</span><span class="n">videos</span><span class="o">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">`</span><span class="n">videos</span><span class="o">`</span><span class="p">.</span><span class="o">`</span><span class="n">id</span><span class="o">`</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">AND</span><span class="w"> </span><span class="o">`</span><span class="n">videos</span><span class="o">`</span><span class="p">.</span><span class="o">`</span><span class="n">commentable_type</span><span class="o">`</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">?</span></span></span></code></pre>
  </div>
  


<p>This is obviously not what we want, as <code>commentable_type</code> is a column in the <code>comments</code> table, not
in the <code>videos</code> table.</p>
<p>The upside is that we now know that by the time the SQL is generated, Laravel already knows what the
related model is. We can use this to our advantage by preventing it from loading the related model
if it is not of the type we want.</p>
<p>For the final solution, I want to be able to define a relation just like I would any other, but
specify which type it needs to be and result in <code>null</code> otherwise.</p>
<p>In order to do that, I started looking into the source code for <code>MorphTo</code>. When exploring
<a href="https://github.com/laravel/framework/blob/a9bfc0d37d1580bdafe39735b12fa8656fb3f24b/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php#L392-L418" title=" HasRelation::morphTo" target="_blank" rel="nofollow noopener"> <code>HasRelation::morphTo</code></a>,
I found that eager loaded relations are loaded differently than lazy loaded relations. This means we
need to solve two different problems.</p>
<h3 id="lazy-loaded-relations" class="link-owner">
  Lazy loaded relations
</h3>
  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="nx">Comment</span><span class="o">::</span><span class="na">first</span><span class="p">()</span><span class="o">-&gt;</span><span class="na">commentable</span><span class="p">;</span></span></span></code></pre>
  </div>
  


<p>Lazy loaded relations are very straightforward. When lazy loading, Laravel will call
<a href="https://github.com/laravel/framework/blob/9866967a1e7f5a00f4fe71acf558098d860464f4/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php#L77-L85" title=" getResults" target="_blank" rel="nofollow noopener"> <code>getResults</code></a>
on the relation instance. For each relation, this method simply executes the query and returns the
first result. Unless we prevent it when the query is not for the model we want, of course!</p>
<h3 id="eager-loaded-relations" class="link-owner">
  Eager loaded relations
</h3>
  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="nx">Comment</span><span class="o">::</span><span class="na">with</span><span class="p">(</span><span class="s1">&#39;commentable&#39;</span><span class="p">)</span><span class="o">-&gt;</span><span class="na">first</span><span class="p">();</span></span></span></code></pre>
  </div>
  


<p>Eager loaded relations are a little more involved. Laravel will call
<a href="https://github.com/laravel/framework/blob/a9bfc0d37d1580bdafe39735b12fa8656fb3f24b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php#L94-L99" title=" addEagerConstraints" target="_blank" rel="nofollow noopener"> <code>addEagerConstraints</code></a>
on the relation instance, which in <code>MorphTo</code>’s case calls
<a href="https://github.com/laravel/framework/blob/a9bfc0d37d1580bdafe39735b12fa8656fb3f24b/src/Illuminate/Database/Eloquent/Relations/MorphTo.php#L101-L117" title=" buildDictionary" target="_blank" rel="nofollow noopener"> <code>buildDictionary</code></a>
. This method is responsible for determining what model to load for each eager loaded relation.</p>
<h2 id="implementing-final-solution" class="link-owner">
  Implementing final solution
</h2><p>The final solution consists of a class and a trait, which together allow you to use the relation in
the same way as you would other relation.</p>
<h3 id="constrainedmorphto" class="link-owner">
  ConstrainedMorphTo
</h3><p>This class extends <code>MorphTo</code> and overrides the <code>buildDictionary</code> and <code>getResults</code> methods to prevent
any model other than <code>$allowedType</code> to be loaded.</p>
<p>In the case of <code>buildDictionary</code>, we prevent the model from being added to the dictionary if
<code>$model-&gt;{$this-&gt;morphType}</code> does not equal <code>$allowedType</code>. This means that the model will not end
up in the list of relations to load eagerly.</p>
<p>The <code>getResults</code> method is also overridden to simply return <code>null</code> if the <code>morphType</code> does not equal
<code>$allowedType</code>, instead of executing the database query.</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">declare</span><span class="p">(</span><span class="nx">strict_types</span><span class="o">=</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nx">App\Relations</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Database\Eloquent\Builder</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Database\Eloquent\Collection</span> <span class="k">as</span> <span class="nx">EloquentCollection</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Database\Eloquent\Model</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Database\Eloquent\Relations\MorphTo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Override</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd"> * @template TRelatedModel of Model
</span></span></span><span class="line"><span class="cl"><span class="sd"> * @template TDeclaringModel of Model
</span></span></span><span class="line"><span class="cl"><span class="sd"> *
</span></span></span><span class="line"><span class="cl"><span class="sd"> * @extends MorphTo&lt;TRelatedModel, TDeclaringModel&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd"> */</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ConstrainedMorphTo</span> <span class="k">extends</span> <span class="nx">MorphTo</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @var class-string&lt;TRelatedModel&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">     */</span>
</span></span><span class="line"><span class="cl">    <span class="k">protected</span> <span class="nx">readonly</span> <span class="nx">string</span> <span class="nv">$allowedType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  Builder&lt;TRelatedModel&gt;      $query
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  TDeclaringModel             $parent
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  string                      $foreignKey
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  string                      $ownerKey
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  string                      $type
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  string                      $relation
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  class-string&lt;TRelatedModel&gt; $constrainedTo
</span></span></span><span class="line"><span class="cl"><span class="sd">     */</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="fm">__construct</span><span class="p">(</span><span class="nx">Builder</span> <span class="nv">$query</span><span class="p">,</span> <span class="nv">$parent</span><span class="p">,</span> <span class="nv">$foreignKey</span><span class="p">,</span> <span class="nv">$ownerKey</span><span class="p">,</span> <span class="nv">$type</span><span class="p">,</span> <span class="nv">$relation</span><span class="p">,</span> <span class="nx">string</span> <span class="nv">$constrainedTo</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">allowedType</span> <span class="o">=</span> <span class="nv">$constrainedTo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">parent</span><span class="o">::</span><span class="na">__construct</span><span class="p">(</span><span class="nv">$query</span><span class="p">,</span> <span class="nv">$parent</span><span class="p">,</span> <span class="nv">$foreignKey</span><span class="p">,</span> <span class="nv">$ownerKey</span><span class="p">,</span> <span class="nv">$type</span><span class="p">,</span> <span class="nv">$relation</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">#[Override]
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">protected</span> <span class="k">function</span> <span class="nf">buildDictionary</span><span class="p">(</span><span class="nx">EloquentCollection</span> <span class="nv">$models</span><span class="p">)</span><span class="o">:</span> <span class="nx">void</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="nv">$models</span> <span class="k">as</span> <span class="nv">$model</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nv">$model</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">morphType</span><span class="p">}</span> <span class="o">!==</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">allowedType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nv">$model</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">morphType</span><span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$morphTypeKey</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">getDictionaryKey</span><span class="p">(</span><span class="nv">$model</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">morphType</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">                <span class="nv">$foreignKeyKey</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">getDictionaryKey</span><span class="p">(</span><span class="nv">$model</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">foreignKey</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">dictionary</span><span class="p">[</span><span class="nv">$morphTypeKey</span><span class="p">][</span><span class="nv">$foreignKeyKey</span><span class="p">][]</span> <span class="o">=</span> <span class="nv">$model</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sd">/** @inheritDoc */</span>
</span></span><span class="line"><span class="cl">    <span class="c1">#[Override]
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">function</span> <span class="nf">getResults</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">parent</span><span class="o">-&gt;</span><span class="p">{</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">morphType</span><span class="p">}</span> <span class="o">!==</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">allowedType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="k">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">parent</span><span class="o">::</span><span class="na">getResults</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  


<h3 id="hasconstrainedmorphto" class="link-owner">
  HasConstrainedMorphTo
</h3><p>This trait provides the method <code>constrainedMorphTo</code>, which handles loading setting up everything
needed to create a new <code>ConstrainedMorphTo</code> instance, whether it is eager or lazy loaded. It has an
extra <code>constrainedTo</code> parameter, which is the class name of the model you want to allow. In
addition, it always requires the <code>type</code> and <code>id</code> parameters, as the names of these columns are never
the same as the <code>morphTo</code> relation name, or we wouldn’t need this method in the first place.</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">declare</span><span class="p">(</span><span class="nx">strict_types</span><span class="o">=</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nx">App\Traits</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">App\Relations\ConstrainedMorphTo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Database\Eloquent\Model</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd"> * @mixin Model
</span></span></span><span class="line"><span class="cl"><span class="sd"> */</span>
</span></span><span class="line"><span class="cl"><span class="k">trait</span> <span class="nx">HasConstrainedMorphTo</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd">     * Define a polymorphic, inverse one-to-one or many relationship, which only allows a specific type of model to be related.
</span></span></span><span class="line"><span class="cl"><span class="sd">     *
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @template TRelatedModel of Model
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  class-string&lt;TRelatedModel&gt;  $constrainedTo
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  string                       $type
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  string                       $id
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  string|null                  $name
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  string|null                  $ownerKey
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @return ConstrainedMorphTo&lt;TRelatedModel, $this&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">     */</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">constrainedMorphTo</span><span class="p">(</span><span class="nx">string</span> <span class="nv">$constrainedTo</span><span class="p">,</span> <span class="nx">string</span> <span class="nv">$type</span><span class="p">,</span> <span class="nx">string</span> <span class="nv">$id</span><span class="p">,</span> <span class="o">?</span><span class="nx">string</span> <span class="nv">$name</span> <span class="o">=</span> <span class="k">null</span><span class="p">,</span> <span class="o">?</span><span class="nx">string</span> <span class="nv">$ownerKey</span> <span class="o">=</span> <span class="k">null</span><span class="p">)</span><span class="o">:</span> <span class="nx">ConstrainedMorphTo</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// If no name is provided, the backtrace will be used to get the function name
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// since that is most likely the name of the polymorphic interface.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$name</span> <span class="o">=</span> <span class="nv">$name</span> <span class="o">?:</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">guessBelongsToRelation</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// If the type value is null, it is probably safe to assume the relationship is being eagerly loading
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// the relationship. In this case we will just pass in a dummy query where we
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// need to remove any eager loads that may already be defined on a model.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nv">$class</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">getAttributeFromArray</span><span class="p">(</span><span class="nv">$type</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$class</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$query</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">newQuery</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$instance</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">newRelatedInstance</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="k">static</span><span class="o">::</span><span class="na">getActualClassNameForMorph</span><span class="p">(</span><span class="nv">$class</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$query</span> <span class="o">=</span> <span class="nv">$instance</span><span class="o">-&gt;</span><span class="na">newQuery</span><span class="p">()</span><span class="o">-&gt;</span><span class="na">setEagerLoads</span><span class="p">([]);</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$ownerKey</span> <span class="o">??=</span> <span class="nv">$instance</span><span class="o">-&gt;</span><span class="na">getKeyName</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">new</span> <span class="nx">ConstrainedMorphTo</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nx">query</span><span class="o">:</span> <span class="nv">$query</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="k">parent</span><span class="o">:</span> <span class="nv">$this</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">foreignKey</span><span class="o">:</span> <span class="nv">$id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">ownerKey</span><span class="o">:</span> <span class="nv">$ownerKey</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">type</span><span class="o">:</span> <span class="nv">$type</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">relation</span><span class="o">:</span> <span class="nv">$name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">constrainedTo</span><span class="o">:</span> <span class="nv">$constrainedTo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  


<h2 id="usage" class="link-owner">
  Usage
</h2><p>You can use the <code>constrainedMorphTo</code> relation in the same way as you would use a normal <code>morphTo</code>
relation, except you specify which type of model is the only type allowed. The <code>type</code> and <code>id</code>
parameters are required, as they won’t be the same as the relation name and therefore can’t be
inferred.</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Comment</span> <span class="k">extends</span> <span class="nx">Model</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">use</span> <span class="nx">HasConstrainedMorphTo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Not required, but shown as reference
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">public</span> <span class="k">function</span> <span class="nf">commentable</span><span class="p">()</span><span class="o">:</span> <span class="nx">MorphTo</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">morphTo</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">video</span><span class="p">()</span><span class="o">:</span> <span class="nx">MorphTo</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">constrainedMorphTo</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="nx">constrainedTo</span><span class="o">:</span> <span class="nx">Video</span><span class="o">::</span><span class="na">class</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">type</span><span class="o">:</span> <span class="s1">&#39;commentable_type&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">id</span><span class="o">:</span> <span class="s1">&#39;commentable_id&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  



  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="nv">$comment</span> <span class="o">=</span> <span class="nx">Comment</span><span class="o">::</span><span class="na">factory</span><span class="p">()</span><span class="o">-&gt;</span><span class="na">withPost</span><span class="p">()</span><span class="o">-&gt;</span><span class="na">create</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="nv">$comment</span><span class="o">-&gt;</span><span class="na">commentable</span><span class="p">;</span> <span class="c1">// returns a Post object
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nv">$comment</span><span class="o">-&gt;</span><span class="na">video</span><span class="p">;</span>       <span class="c1">// returns null, as the commentable is not a Video
</span></span></span></code></pre>
  </div>
  


<h2 id="conclusion" class="link-owner">
  Conclusion
</h2><p>There you have it! A way to constrain a <code>MorphTo</code> relation to a specific model type without making
extra database queries. This solution works for both lazy and eager loaded relations, and you can
use it just like you would any other relation.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://github.com/laravel/framework/discussions/52336" title="Specify MorphTo model · laravel/framework · Discussion #52336" target="_blank" rel="nofollow noopener">Specify MorphTo model · laravel/framework · Discussion #52336</a>&#160;<a href="https://hansvl.nl/blog/constraining-laravels-morph-to-relation/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

              ]]></description></item><item><title>Advanced Kovarex enrichment process</title><link>https://hansvl.nl/blog/advanced-kovarex-enrichment-process/</link><pubDate>Fri, 10 May 2024 21:51:59 +0200</pubDate><author>Hans van Luttikhuizen-Ross</author><description><![CDATA[ <p><img align="left" hspace="5" style="margin-bottom: 1rem;" src="https://hansvl.nl/blog/advanced-kovarex-enrichment-process/tileable-twelve-beacon-kovarex-enrichment_cover_hu_e41c9e6c5e999ae2.png"/></p><blockquote>
<p><strong>TL;DR:</strong> We take the happy rocks out of the centrifuge and route them back in as long as they are in a
quantity divisible by 10. When they are not, we take them out one by one until they are again.</p>
</blockquote>
<p>The <a href="https://wiki.factorio.com/Kovarex_enrichment_process" title="Kovarex enrichment process" target="_blank" rel="nofollow noopener">Kovarex enrichment process</a> is a very
interesting recipe in Factorio. Most recipes in the game take a certain amount of one or more
ingredients and output a single product.</p>
<p>The Kovarex enrichment process, however, turns 40
<a href="https://wiki.factorio.com/Uranium-235" title="uranium-235" target="_blank" rel="nofollow noopener">uranium-235</a> (from here on ‘happy rocks’) and 5
<a href="https://wiki.factorio.com/Uranium-238" title="uranium-238" target="_blank" rel="nofollow noopener">uranium-238</a> (from here on ‘sad rocks’) into 41 happy rocks
and 2 sad rocks. It effectively converts 3 sad rocks into 1 happy rock. It is precisely because you
have to take these ‘extra’ happy rocks out of the process while putting the rest back in that makes
it interesting.</p>
<div>
    <video
      class="mb-0"
      controls
      preload="auto"
      width="100%"
      
      autoplay
      loop
      muted
      
      playsinline
    >
        <source src="/blog/advanced-kovarex-enrichment-process/twelve-beacon-kovarex-enrichment.mp4" type="video/mp4" />
      <span></span>
    </video>
    
      <figcaption class="!lg:-mt-4 text-right italic">The second happy rock is due to the efficiency modules and normally doesn’t occur every time. The looped animation has been shortened.</figcaption>
    
  </div>
<p>The process is primed with 40 happy rocks. Since we get one or two happy rocks more out of the
process than we put in, we want to have a system that ensures <em>only</em> excess rocks are taken out.</p>
<p>Obviously, we could just take all the rocks—both happy and sad—through a belt and route them back to
an input. But that would be boring, so let’s use circuits instead.</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/advanced-kovarex-enrichment-process/process-layout-with-numbers.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/advanced-kovarex-enrichment-process/process-layout-with-numbers_hu_4e6a80b27b2d8abf.webp" />
          <source media="(max-width: 767px)" srcset="/blog/advanced-kovarex-enrichment-process/process-layout-with-numbers_hu_172d5993b8b8119f.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/advanced-kovarex-enrichment-process/process-layout-with-numbers_hu_8fb3ff44d56f180b.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/advanced-kovarex-enrichment-process/process-layout-with-numbers_hu_b336787e81e9cb14.webp" />
          <img
            class="rounded-lg mx-auto"
            title="The Kovarex enrichment process layout with numbers"
            alt="The numbers are ordered in order to explain the process, not to denote the chronological order of the process."
            loading="lazy"
            src="/blog/advanced-kovarex-enrichment-process/process-layout-with-numbers.png"
            
              style=" background: linear-gradient(-30deg, #3d3835,
              #c1beb5); background-size: cover; "
            
            width="960"
            height="960"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        The numbers are ordered in order to explain the process, not to denote the chronological order of the process.
      </figcaption>
    </figure>
  


</p>
<ol>
<li>This chest contains everything needed for the process; 40 happy rocks and a few sad rocks.
Because inserters will fill the centrifuge to 80 happy rocks, this means there are a total of 120
happy rocks in this system.<!-- raw HTML omitted --></li>
<li>This inserter inserts a limited amount of sad rocks into chest ① so that there’s always enough to
convert into happy rocks.</li>
<li>This chest is where the centrifuge will output its products. Its contents are sent to modulo <code>%</code>
arithmetic combinator ⑤.</li>
<li>Inserters only ever take out one type of item at a time, so by limiting the hand size to 10, we
know for sure that happy rocks will be taken out 10 at a time. This is important.</li>
<li>This modulo <code>%</code> arithmetic combinator divides the signal by 10 and outputs the remainder. The
output is 0 when the signal is divisible by 10.</li>
<li>This filter inserter only takes out happy rocks when the output of combinator ⑤ is greater
than 0.</li>
<li>This stack inserter takes out both happy rocks and sad rocks if <em>happy rocks mod 10 = 0</em>,
according to combinator ⑤. When combinator ⑤ outputs 1 or more, this inserter is disabled, and
filter inserter ⑥ takes over.</li>
</ol>
<p>So, chronologically:</p>
<p>The centrifuge finishes, stack inserter ④ takes out the sad rocks, 4 stacks of 10 happy rocks and
one stack of 1 happy rock. The sad rocks are immediately passed along as 0 mod 10 is 0. The stacks
of 10 are also passed along, since <em>10 mod 10 is</em> also 0.</p>
<p>After the last stack of happy rocks is put into the chest, there is a non-divisible-by-ten amount of
happy rocks, so filter inserter ⑦ stops and filter inserter ⑥ starts taking out happy rocks until
the amount of <em>happy rocks in chest ③ mod 10</em> is 0 again.</p>
<p>There is one little snag. When there is one happy rock in chest ③, it will send a signal of 1 to
combinator ⑤, which will then send a signal of 1 to filter inserter ⑦ the next frame. But by then,
the happy rock has already been taken out by filter inserter ⑦. This is easily solved by reading the
hand contents of stack inserter ④ and sending that signal with <code>hold</code> (not <code>pulse</code>) to combinator ⑤
as well, effectively making its contents an extension of chest ③.</p>
<p>We now have a closed system that only takes new sad rocks as they are needed and only ever outputs
the excess happy rocks.</p>

              ]]></description></item><item><title>Power shutoff automation in Factorio</title><link>https://hansvl.nl/blog/power-shutoff-automation-in-factorio/</link><pubDate>Sun, 07 Apr 2024 12:03:10 +0200</pubDate><lastBuildDate>Fri, 10 May 2024 23:37:21 +0200</lastBuildDate><author>Hans van Luttikhuizen-Ross</author><description><![CDATA[ <p><img align="left" hspace="5" style="margin-bottom: 1rem;" src="https://hansvl.nl/blog/power-shutoff-automation-in-factorio/accumulator-ui_cover_hu_a0911525ce89c177.png"/></p><h1 id="our-goal" class="link-owner">
  Our goal
</h1><p>In Factorio, when you start building solar panels, you will quickly start to resent the coal power
plants constantly burning fuel when they shouldn’t be. Let’s assume you have
<a href="https://wiki.factorio.com/Accumulator" title="accumulators" target="_blank" rel="nofollow noopener">accumulators</a> to store the power your solar panels provide,
and that you have enough to get you through the night. Most of the time, at least.</p>
<p>The accumulators provide the current charge percentage as a signal, so like the good Factorio
engineer you are, you connect your accumulator to your
<a href="https://wiki.factorio.com/Offshore_pump" title="offshore pump" target="_blank" rel="nofollow noopener">offshore pump</a> and set it to enable only when the
accumulators are below some charge level. Luckily all accumulators in a power network always have
the same amount of charge.</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/power-shutoff-automation-in-factorio/simple-automation.jpg">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/power-shutoff-automation-in-factorio/simple-automation_hu_5e239a0751622c9e.webp" />
          <source media="(max-width: 767px)" srcset="/blog/power-shutoff-automation-in-factorio/simple-automation_hu_74a7eafe87b68cf3.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/power-shutoff-automation-in-factorio/simple-automation_hu_b5c8003db8a71870.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/power-shutoff-automation-in-factorio/simple-automation_hu_53c51cae77627f9a.webp" />
          <img
            class="rounded-lg mx-auto"
            title="Simple automation"
            alt="The offshore pump is shut off because the accumulators have enough charge."
            loading="lazy"
            src="/blog/power-shutoff-automation-in-factorio/simple-automation.jpg"
            
              style=" background: linear-gradient(-30deg, #433f30,
              #977041); background-size: cover; "
            
            width="1920"
            height="1080"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        The offshore pump is shut off because the accumulators have enough charge.
      </figcaption>
    </figure>
  


</p>
<p>This works, but this results in the power level hovering around 30%, with the steam engines
flickering on and off as the accumulators charge and discharge.</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/power-shutoff-automation-in-factorio/coal-power-flickering.jpg">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/power-shutoff-automation-in-factorio/coal-power-flickering_hu_d83c883934c3ee3e.webp" />
          <source media="(max-width: 767px)" srcset="/blog/power-shutoff-automation-in-factorio/coal-power-flickering_hu_f2f46cbd89836e48.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/power-shutoff-automation-in-factorio/coal-power-flickering_hu_747921698b577911.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/power-shutoff-automation-in-factorio/coal-power-flickering_hu_dd3e849f562863ba.webp" />
          <img
            class="rounded-lg mx-auto"
            title="Coal power flickering"
            alt="The chart shows the coal power turning off and on around the 30% mark."
            loading="lazy"
            src="/blog/power-shutoff-automation-in-factorio/coal-power-flickering.jpg"
            
              style=" background: linear-gradient(-30deg, #383731,
              #956a41); background-size: cover; "
            
            width="1920"
            height="1080"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        The chart shows the coal power turning off and on around the 30% mark.
      </figcaption>
    </figure>
  


</p>
<p>We can do better. We can have the offshore pump turn on, and then have it run until it reaches a
certain percentage—say 80%. We can already turn the offshore pump on, but how do we decide when to
shut it off again?</p>
<p>Factorio offers several <a href="https://wiki.factorio.com/Combinators" title="combinators" target="_blank" rel="nofollow noopener">combinators</a> to work with basic
signal logic. So what we could do is turn the pump on and then use an <code>AND</code> gate to turn the pump
off when the pump is on and the accumulator charge is over 80%.</p>
<p>Unfortunately, neither the offshore pump, the boiler nor the steam turbines provide us with a
signal<sup id="fnref:1"><a href="https://hansvl.nl/blog/power-shutoff-automation-in-factorio/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> to tell us whether they’re on or off.</p>
<h1 id="flip-flops" class="link-owner">
  Flip-flops
</h1><p>Enter <a href="https://simple.wikipedia.org/wiki/Flip-flop_%28electronics%29" title="flip-flops" target="_blank" rel="nofollow noopener">flip-flops</a>. Flip-flops, also
known as latches, are electronic circuits with two possible stable states, <code>1</code> or <code>0</code>, on or off.
Being stable means they ‘remember’ their state, which is why these are building blocks of computer
memory. This is, in effect, a 1-bit memory cell.</p>
<p>Please note that I do not have any electrical engineering background, and as such I will be talking
about these from a programming perspective, not an electrical one. With that out of the way:</p>
<p>Flip-flops have two inputs, and one output. The inputs are called ‘set’ and ‘reset’. When a signal
is sent to the <em>set</em> input, the input turns on if it wasn&rsquo;t already. If you then send a signal to
the <em>reset</em> input, it turns off again.</p>
<p>For a visual explanation by one of my favourite YouTubers Sebastian Lague, please watch this
timestamped section of the video called
<a href="https://youtu.be/I0-izyq6q5s?t=73" title="How Do Computers Remember?" target="_blank" rel="nofollow noopener">How Do Computers Remember?</a>.</p>
<h1 id="implementing-a-flip-flop-in-factorio" class="link-owner">
  Implementing a flip-flop in Factorio
</h1><p>With Factorio’s combinators, we have all the tools to build our own flip-flop. We’re setting out to
remember whether the offshore pump is providing water to our coal generator, to be able to determine
when to shut it off again.</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/power-shutoff-automation-in-factorio/factorio-flip-flop.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/power-shutoff-automation-in-factorio/factorio-flip-flop_hu_78774a3228ec68dd.webp" />
          <source media="(max-width: 767px)" srcset="/blog/power-shutoff-automation-in-factorio/factorio-flip-flop_hu_db9952697d4f7400.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/power-shutoff-automation-in-factorio/factorio-flip-flop_hu_16915799d65cfa4a.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/power-shutoff-automation-in-factorio/factorio-flip-flop_hu_390fd11c79103488.webp" />
          <img
            class="rounded-lg mx-auto"
            title="Factorio flip-flop"
            alt="A flip-flop implemented using Factorio’s combinators."
            loading="lazy"
            src="/blog/power-shutoff-automation-in-factorio/factorio-flip-flop.png"
            
              style=" background: linear-gradient(-30deg, #3c4524,
              #b08750); background-size: cover; "
            
            width="668"
            height="668"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        A flip-flop implemented using Factorio’s combinators.
      </figcaption>
    </figure>
  


</p>
<p>The accumulator is sending a signal to two arithmetic combinators.</p>
<ol>
<li>
<p>The accumulator signal is sent to a decider combinator that outputs 1 when that signal is <code>&lt;</code>
30%. This is our <code>set</code> signal.</p>
</li>
<li>
<p>The accumulator signal is also sent to a decider combinator that outputs 1 when the signal is
<code>&gt;=</code> 80%. This is our <code>reset</code> signal.</p>
</li>
<li>
<p>This arithmetic combinator performs a boolean <code>OR</code> (<code>|</code>) on the <code>set</code> signal and the output of
this flip-flop. This combination is what causes the steady state, as you will see.</p>
</li>
<li>
<p>This decider combinator takes the reset signal and performs a boolean <code>NOT</code> (<code>!=</code>) on the signal.
This way, if no signal is sent to the <code>reset</code> input, we send a <code>1</code> and vice versa.</p>
</li>
<li>
<p>This final decider combinator performs a boolean <code>AND</code> on the output of the <code>set</code> and the
(<code>NOT</code>ed) <code>reset</code> signal. As long as the signal is <code>set</code> and <code>NOT reset</code>, we output <code>1</code>.</p>
<p>This <code>1</code> is also sent back to the <code>OR</code> combinator <code>3</code>, which will keep the flip-flop in the <code>1</code>
state.</p>
</li>
</ol>
<p>The final step is to send the output of that final decider combinator—the output of our flip-flop—to
our offshore pump, and voilà! We have successfully buffered our signal!</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/power-shutoff-automation-in-factorio/generator-on-when-it-should-be.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/power-shutoff-automation-in-factorio/generator-on-when-it-should-be_hu_6a0d18d706980aa2.webp" />
          <source media="(max-width: 767px)" srcset="/blog/power-shutoff-automation-in-factorio/generator-on-when-it-should-be_hu_670be58599795a36.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/power-shutoff-automation-in-factorio/generator-on-when-it-should-be_hu_4bd07f89b258e525.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/power-shutoff-automation-in-factorio/generator-on-when-it-should-be_hu_2c5672d7536ea10d.webp" />
          <img
            class="rounded-lg mx-auto"
            title="Generator is on when it should be!"
            alt="The coal generator is on, even though there is more than 30% charge in the accumulators."
            loading="lazy"
            src="/blog/power-shutoff-automation-in-factorio/generator-on-when-it-should-be.png"
            
              style=" background: linear-gradient(-30deg, #413f35,
              #94734e); background-size: cover; "
            
            width="1920"
            height="1080"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        The coal generator is on, even though there is more than 30% charge in the accumulators.
      </figcaption>
    </figure>
  


</p>
<hr>
<p><strong>Update (2024-05-10):</strong></p>
<p>When reading through the
<a href="https://wiki.factorio.com/Tutorial:Circuit_network_cookbook#Latches" title="Circuit network cookbook" target="_blank" rel="nofollow noopener">Circuit network cookbook</a>, I
found out that you can make a flip-flop using just one decider combinator. By running a signal wire
from the output back to the input, you can create a flip-flop that remembers its state.</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/power-shutoff-automation-in-factorio/single-decider-flip-flop.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/power-shutoff-automation-in-factorio/single-decider-flip-flop_hu_a9112cd842b40fbc.webp" />
          <source media="(max-width: 767px)" srcset="/blog/power-shutoff-automation-in-factorio/single-decider-flip-flop_hu_af399fcf736a90b7.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/power-shutoff-automation-in-factorio/single-decider-flip-flop_hu_8d3ef6812b1c1531.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/power-shutoff-automation-in-factorio/single-decider-flip-flop_hu_ef81f23757241953.webp" />
          <img
            class="rounded-lg mx-auto"
            title="Single decider flip-flop"
            alt="A flip-flop implemented using a single decider combinator."
            loading="lazy"
            src="/blog/power-shutoff-automation-in-factorio/single-decider-flip-flop.png"
            
              style=" background: linear-gradient(-30deg, #373737,
              #a3a9ac); background-size: cover; "
            
            width="1227"
            height="656"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        A flip-flop implemented using a single decider combinator.
      </figcaption>
    </figure>
  


</p>
<p>The only downside of this approach is that the output signal (in this case <code>S</code>) is always the same
as the input signal, rather than allowing you to choose any signal you want.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>A nice overview of which devices send and/or receive which signals can be found
<a href="https://wiki.factorio.com/Circuit_network#Devices" title="here" target="_blank" rel="nofollow noopener">here</a>.&#160;<a href="https://hansvl.nl/blog/power-shutoff-automation-in-factorio/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

              ]]></description></item><item><title>Days of week field in Laravel</title><link>https://hansvl.nl/blog/laravel-days-of-week-field/</link><pubDate>Fri, 05 Apr 2024 15:48:28 +0200</pubDate><lastBuildDate>Wed, 09 Apr 2025 14:45:53 +0200</lastBuildDate><author>Hans van Luttikhuizen-Ross</author><description><![CDATA[ <p><img align="left" hspace="5" style="margin-bottom: 1rem;" src="https://hansvl.nl/blog/laravel-days-of-week-field/routine-schedule_cover_hu_307796010ef274e5.png"/></p><div>
  <h2>Table of Contents</h2>
  <nav id="TableOfContents">
  <ul>
    <li><a href="https://hansvl.nl/blog/laravel-days-of-week-field/#the-context">The context</a></li>
    <li><a href="https://hansvl.nl/blog/laravel-days-of-week-field/#the-implementation">The implementation</a>
      <ul>
        <li><a href="https://hansvl.nl/blog/laravel-days-of-week-field/#enum">Enum</a></li>
        <li><a href="https://hansvl.nl/blog/laravel-days-of-week-field/#custom-cast">Custom cast</a></li>
      </ul>
    </li>
    <li><a href="https://hansvl.nl/blog/laravel-days-of-week-field/#why-this-is-a-bad-idea">Why this is a bad idea</a></li>
  </ul>
</nav>
</div>

<h2 id="the-context" class="link-owner">
  The context
</h2><p>For a local butchery chain, I created an intranet environment to maintain cleaning schedules, manage
deliveries, and more. I built the intranet from the ground up, from software architecture and
<code>git init</code> to DTAP<sup id="fnref:1"><a href="https://hansvl.nl/blog/laravel-days-of-week-field/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> deployment and continued development.</p>
<p>One of the uses of the intranet was to manage cleaning schedules. These tasks were almost all
planned to happen on multiple—but not all— days of the week, every week.</p>
<h2 id="the-implementation" class="link-owner">
  The implementation
</h2><p>How do you store a schedule that repeats weekly on one or more days? The first solution I came up
with was to store an array of days of the week in a single column. I must have seen this recently,
and ended up using it in this project.</p>
<p>Laravel offers a way to define custom casts for your models<sup id="fnref:2"><a href="https://hansvl.nl/blog/laravel-days-of-week-field/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. This enables you to define
how a value should be stored in a database column, and how it should be retrieved back into that
value.</p>
<h3 id="enum" class="link-owner">
  Enum
</h3><p>The first step was to create an int backed enum for the days of the week:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="nx">enum</span> <span class="nx">Day</span><span class="o">:</span> <span class="nx">int</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">Monday</span>    <span class="o">=</span> <span class="mi">0</span><span class="nx">b00000001</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">Tuesday</span>   <span class="o">=</span> <span class="mi">0</span><span class="nx">b00000010</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">Wednesday</span> <span class="o">=</span> <span class="mi">0</span><span class="nx">b00000100</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">Thursday</span>  <span class="o">=</span> <span class="mi">0</span><span class="nx">b00001000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">Friday</span>    <span class="o">=</span> <span class="mi">0</span><span class="nx">b00010000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">Saturday</span>  <span class="o">=</span> <span class="mi">0</span><span class="nx">b00100000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nx">Sunday</span>    <span class="o">=</span> <span class="mi">0</span><span class="nx">b01000000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  


<p>We would then have to be able to get a collection of these Days from an integer, and the other way
around:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd"> * @return Collection&lt;array-key, Day&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd"> */</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">collectionFromInt</span><span class="p">(</span><span class="nx">int</span> <span class="nv">$value</span><span class="p">)</span><span class="o">:</span> <span class="nx">Collection</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">collect</span><span class="p">(</span><span class="nx">Day</span><span class="o">::</span><span class="na">cases</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="o">-&gt;</span><span class="na">filter</span><span class="p">(</span><span class="nx">fn</span> <span class="p">(</span><span class="nx">Day</span> <span class="nv">$day</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nv">$value</span> <span class="o">&amp;</span> <span class="nv">$day</span><span class="o">-&gt;</span><span class="na">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">intFromCollection</span><span class="p">(</span><span class="nx">Collection</span> <span class="nv">$days</span><span class="p">)</span><span class="o">:</span> <span class="nx">int</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nv">$days</span><span class="o">-&gt;</span><span class="na">reduce</span><span class="p">(</span><span class="nx">fn</span> <span class="p">(</span><span class="nx">int</span> <span class="nv">$acc</span><span class="p">,</span> <span class="nx">Day</span> <span class="nv">$day</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nv">$acc</span> <span class="o">|</span> <span class="nv">$day</span><span class="o">-&gt;</span><span class="na">value</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  


<h3 id="custom-cast" class="link-owner">
  Custom cast
</h3><p>With the enum ready, we can now define a custom cast in <code>App\Casts</code>:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nx">App\Casts</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">App\Enums\Day</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Contracts\Database\Eloquent\CastsAttributes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Database\Eloquent\Model</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">use</span> <span class="nx">Illuminate\Support\Collection</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DaysOfWeek</span> <span class="k">implements</span> <span class="nx">CastsAttributes</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param  int|null $value
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @return Collection&lt;array-key, Day&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">     */</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">get</span><span class="p">(</span><span class="nx">Model</span> <span class="nv">$model</span><span class="p">,</span> <span class="nx">string</span> <span class="nv">$key</span><span class="p">,</span> <span class="nx">mixed</span> <span class="nv">$value</span><span class="p">,</span> <span class="k">array</span> <span class="nv">$attributes</span><span class="p">)</span><span class="o">:</span> <span class="nx">Collection</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">Day</span><span class="o">::</span><span class="na">collectionFromInt</span><span class="p">(</span><span class="nv">$value</span> <span class="o">??</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd">     * @param Collection&lt;array-key, Day&gt; $value
</span></span></span><span class="line"><span class="cl"><span class="sd">     */</span>
</span></span><span class="line"><span class="cl">    <span class="k">public</span> <span class="k">function</span> <span class="nf">set</span><span class="p">(</span><span class="nx">Model</span> <span class="nv">$model</span><span class="p">,</span> <span class="nx">string</span> <span class="nv">$key</span><span class="p">,</span> <span class="nx">mixed</span> <span class="nv">$value</span><span class="p">,</span> <span class="k">array</span> <span class="nv">$attributes</span><span class="p">)</span><span class="o">:</span> <span class="nx">int</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">Day</span><span class="o">::</span><span class="na">intFromCollection</span><span class="p">(</span><span class="nv">$value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  


<h2 id="why-this-is-a-bad-idea" class="link-owner">
  Why this is a bad idea
</h2><p>This approach works, is elegant, and is easy to use. But it isn’t necessary. Serializing and
deserializing days of the week into integers is complexity that serves no purpose<sup id="fnref:3"><a href="https://hansvl.nl/blog/laravel-days-of-week-field/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>. The
data is not human-readable, and it makes it harder to query the database for specific days of the
week.</p>
<p>A much better solution might have been to just use a <code>boolean</code> column for each day of the week. You
can use an
<a href="https://laravel.com/docs/10.x/eloquent-mutators#accessors-and-mutators" title="accessor/mutator" target="_blank" rel="nofollow noopener">accessor/mutator</a> instead
of a cast if you prefer working with a collection of <code>Day</code> enums.</p>
<p>In fact, let’s think of what that could look like:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd"> * @return Attribute&lt;Collection&lt;Day&gt;, void&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd"> */</span>
</span></span><span class="line"><span class="cl"><span class="k">protected</span> <span class="k">function</span> <span class="nf">weekdays</span><span class="p">()</span><span class="o">:</span> <span class="nx">Attribute</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">new</span> <span class="nx">Attribute</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="nx">get</span><span class="o">:</span> <span class="nx">fn</span> <span class="p">()</span><span class="o">:</span> <span class="nx">Collection</span> <span class="o">=&gt;</span> <span class="nx">collect</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">monday</span>    <span class="o">?</span> <span class="nx">Day</span><span class="o">::</span><span class="na">Monday</span>    <span class="o">:</span> <span class="k">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">tuesday</span>   <span class="o">?</span> <span class="nx">Day</span><span class="o">::</span><span class="na">Tuesday</span>   <span class="o">:</span> <span class="k">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">wednesday</span> <span class="o">?</span> <span class="nx">Day</span><span class="o">::</span><span class="na">Wednesday</span> <span class="o">:</span> <span class="k">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">thursday</span>  <span class="o">?</span> <span class="nx">Day</span><span class="o">::</span><span class="na">Thursday</span>  <span class="o">:</span> <span class="k">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">friday</span>    <span class="o">?</span> <span class="nx">Day</span><span class="o">::</span><span class="na">Friday</span>    <span class="o">:</span> <span class="k">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">saturday</span>  <span class="o">?</span> <span class="nx">Day</span><span class="o">::</span><span class="na">Saturday</span>  <span class="o">:</span> <span class="k">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">sunday</span>    <span class="o">?</span> <span class="nx">Day</span><span class="o">::</span><span class="na">Sunday</span>    <span class="o">:</span> <span class="k">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">])</span><span class="o">-&gt;</span><span class="na">filter</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="nx">set</span><span class="o">:</span> <span class="nx">fn</span> <span class="p">(</span><span class="nx">Collection</span> <span class="nv">$value</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">update</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;monday&#39;</span>    <span class="o">=&gt;</span> <span class="nv">$value</span><span class="o">-&gt;</span><span class="na">contains</span><span class="p">(</span><span class="nx">Day</span><span class="o">::</span><span class="na">Monday</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;tuesday&#39;</span>   <span class="o">=&gt;</span> <span class="nv">$value</span><span class="o">-&gt;</span><span class="na">contains</span><span class="p">(</span><span class="nx">Day</span><span class="o">::</span><span class="na">Tuesday</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;wednesday&#39;</span> <span class="o">=&gt;</span> <span class="nv">$value</span><span class="o">-&gt;</span><span class="na">contains</span><span class="p">(</span><span class="nx">Day</span><span class="o">::</span><span class="na">Wednesday</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;thursday&#39;</span>  <span class="o">=&gt;</span> <span class="nv">$value</span><span class="o">-&gt;</span><span class="na">contains</span><span class="p">(</span><span class="nx">Day</span><span class="o">::</span><span class="na">Thursday</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;friday&#39;</span>    <span class="o">=&gt;</span> <span class="nv">$value</span><span class="o">-&gt;</span><span class="na">contains</span><span class="p">(</span><span class="nx">Day</span><span class="o">::</span><span class="na">Friday</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;saturday&#39;</span>  <span class="o">=&gt;</span> <span class="nv">$value</span><span class="o">-&gt;</span><span class="na">contains</span><span class="p">(</span><span class="nx">Day</span><span class="o">::</span><span class="na">Saturday</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;sunday&#39;</span>    <span class="o">=&gt;</span> <span class="nv">$value</span><span class="o">-&gt;</span><span class="na">contains</span><span class="p">(</span><span class="nx">Day</span><span class="o">::</span><span class="na">Sunday</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  


<p>This way, you can still work with a collection of <code>Day</code> enums, what we store in the database is
human-readable, it is easier to query, and it is easier to understand.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>DTAP stands for Development, Testing, Acceptance, Production. It’s a common way to manage
environments in software development.&#160;<a href="https://hansvl.nl/blog/laravel-days-of-week-field/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://laravel.com/docs/eloquent-mutators#custom-casts" title="Laravel: Array &amp; JSON Casting" target="_blank" rel="nofollow noopener">Laravel: Array &amp; JSON Casting</a>&#160;<a href="https://hansvl.nl/blog/laravel-days-of-week-field/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><em>you</em> say: complexity <em>very, very</em> bad<br>
— <a href="https://grugbrain.dev/#lol-lmao" title="The Grug Brained Developer" target="_blank" rel="nofollow noopener">The Grug Brained Developer</a>&#160;<a href="https://hansvl.nl/blog/laravel-days-of-week-field/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

              ]]></description></item><item><title>Provisioning Plesk using Ansible</title><link>https://hansvl.nl/blog/provisioning-plesk-using-ansible/</link><pubDate>Sun, 08 Oct 2023 14:28:52 +0200</pubDate><lastBuildDate>Fri, 05 Apr 2024 11:09:49 +0200</lastBuildDate><author>Hans van Luttikhuizen-Ross</author><description><![CDATA[ <p><img align="left" hspace="5" style="margin-bottom: 1rem;" src="https://hansvl.nl/blog/provisioning-plesk-using-ansible/plesk_cover_hu_9c12200d6e27c62b.png"/></p><div>
  <h2>Table of Contents</h2>
  <nav id="TableOfContents">
  <ul>
    <li><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#introduction">Introduction</a></li>
    <li><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#requirements">Requirements</a></li>
    <li><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#provisioning-a-server">Provisioning a server</a></li>
    <li><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#ansible">Ansible</a>
      <ul>
        <li><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#roles">Roles</a></li>
        <li><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#tags">Tags</a></li>
        <li><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#handlers">Handlers</a></li>
        <li><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#facts">Facts</a></li>
      </ul>
    </li>
    <li><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#final-thoughts">Final thoughts</a></li>
  </ul>
</nav>
</div>

<h2 id="introduction" class="link-owner">
  Introduction
</h2><p>A previous employer was in the business of developing and hosting websites for small and
medium-sized businesses. In order to reduce hosting costs, remove our reliance on an unreliable
third party, give us more control over our offering, and improve our troubleshooting ability, we
wanted to have full control over our web hosting servers.</p>
<h2 id="requirements" class="link-owner">
  Requirements
</h2><p>To achieve those goals, we chose to provision our own web hosting servers. There are multiple
approaches, ranging from fully managed web hosting services—which is what we wanted to step away
from—all the way to on-premises bare metal.</p>
<p>Renting Virtual Private Servers (VPS) gave us the flexibility and control we required, without the
headache of managing the metal. The supplier takes care of making sure the machines are running,
that the HDDs are replaced when nearing the end of their lifetime, that any network outages are
resolved, and so forth. This allows us to focus on precisely the things that we want to do.</p>
<p>Because some customers are either large or security conscious enough to require their own servers, I
had to take into consideration that we would have to set up multiple servers. This would also
provide some risk management, as one server outage would not automatically result in <em>all</em> our
customer’s website being offline, but only a subset, for the duration of the outage.</p>
<p>The servers were going to be used by non-technical people, which meant that the software on them had
to be as intuitive and user-friendly as possible. Moreover, as we didn’t have a full-time server
administrator, I wanted the software to be batteries-included; to offer decent security and
functionality out-of-the-box as much as possible.</p>
<p>This was also the reason that cloud providers like <a href="https://aws.amazon.com/" title="Amazon Web Services" target="_blank" rel="nofollow noopener">Amazon Web Services</a>,
<a href="https://cloud.google.com/" title="Google Cloud Platform" target="_blank" rel="nofollow noopener">Google Cloud Platform</a> or
<a href="https://azure.microsoft.com/" title="Microsoft’s Azure" target="_blank" rel="nofollow noopener">Microsoft’s Azure</a> were not considered. They require expertise that
we didn’t have and probably wouldn’t for the foreseeable future.</p>
<p>Out of the three most popular packages <a href="https://cpanel.net/" title="cPanel" target="_blank" rel="nofollow noopener">cPanel</a>,
<a href="https://www.directadmin.com/" title="DirectAdmin" target="_blank" rel="nofollow noopener">DirectAdmin</a> and <a href="https://www.plesk.com/" title="Plesk" target="_blank" rel="nofollow noopener">Plesk</a>, I chose to use
Plesk for those reasons.</p>
<h2 id="provisioning-a-server" class="link-owner">
  Provisioning a server
</h2><p>‘Provisioning a server’ means setting it up for it’s intended use. This includes—but is not limited
to—configuring hardware, storage, networking, backup and recovery, security, installing software and
extensions, user access control, and so forth.</p>
<p>In practical terms, this means logging into the server using SSH and executing commands on the host
machine. As mentioned earlier, we would have at least a few servers—later nearing a dozen. Keeping
track of which changes were or were not made on which machine was going to require automation.</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 552 233"
      >
      <g transform='translate(8,16)'>
<path d='M 384,0 L 536,0' fill='none' stroke='currentColor'></path>
<path d='M 352,16 L 376,16' fill='none' stroke='currentColor'></path>
<path d='M 0,32 L 288,32' fill='none' stroke='currentColor'></path>
<path d='M 384,32 L 536,32' fill='none' stroke='currentColor'></path>
<path d='M 104,80 L 184,80' fill='none' stroke='currentColor'></path>
<path d='M 384,80 L 536,80' fill='none' stroke='currentColor'></path>
<path d='M 200,96 L 288,96' fill='none' stroke='currentColor'></path>
<path d='M 288,96 L 336,96' fill='none' stroke='currentColor'></path>
<path d='M 336,96 L 376,96' fill='none' stroke='currentColor'></path>
<path d='M 104,112 L 112,112' fill='none' stroke='currentColor'></path>
<path d='M 112,112 L 176,112' fill='none' stroke='currentColor'></path>
<path d='M 176,112 L 184,112' fill='none' stroke='currentColor'></path>
<path d='M 384,112 L 536,112' fill='none' stroke='currentColor'></path>
<path d='M 48,160 L 88,160' fill='none' stroke='currentColor'></path>
<path d='M 88,160 L 104,160' fill='none' stroke='currentColor'></path>
<path d='M 176,160 L 200,160' fill='none' stroke='currentColor'></path>
<path d='M 200,160 L 240,160' fill='none' stroke='currentColor'></path>
<path d='M 384,160 L 536,160' fill='none' stroke='currentColor'></path>
<path d='M 352,176 L 376,176' fill='none' stroke='currentColor'></path>
<path d='M 48,192 L 104,192' fill='none' stroke='currentColor'></path>
<path d='M 176,192 L 240,192' fill='none' stroke='currentColor'></path>
<path d='M 384,192 L 536,192' fill='none' stroke='currentColor'></path>
<path d='M 0,208 L 288,208' fill='none' stroke='currentColor'></path>
<path d='M 0,32 L 0,208' fill='none' stroke='currentColor'></path>
<path d='M 288,32 L 288,96' fill='none' stroke='currentColor'></path>
<path d='M 288,96 L 288,208' fill='none' stroke='currentColor'></path>
<path d='M 336,32 L 336,96' fill='none' stroke='currentColor'></path>
<path d='M 336,96 L 336,160' fill='none' stroke='currentColor'></path>
<path d='M 384,0 L 384,32' fill='none' stroke='currentColor'></path>
<path d='M 384,80 L 384,112' fill='none' stroke='currentColor'></path>
<path d='M 384,160 L 384,192' fill='none' stroke='currentColor'></path>
<path d='M 536,0 L 536,32' fill='none' stroke='currentColor'></path>
<path d='M 536,80 L 536,112' fill='none' stroke='currentColor'></path>
<path d='M 536,160 L 536,192' fill='none' stroke='currentColor'></path>
<path d='M 88,160 L 112,112' fill='none' stroke='currentColor'></path>
<path d='M 176,112 L 200,160' fill='none' stroke='currentColor'></path>
<polygon points='384.000000,16.000000 372.000000,10.400000 372.000000,21.600000' fill='currentColor' transform='rotate(0.000000, 376.000000, 16.000000)'></polygon>
<polygon points='384.000000,96.000000 372.000000,90.400002 372.000000,101.599998' fill='currentColor' transform='rotate(0.000000, 376.000000, 96.000000)'></polygon>
<polygon points='384.000000,176.000000 372.000000,170.399994 372.000000,181.600006' fill='currentColor' transform='rotate(0.000000, 376.000000, 176.000000)'></polygon>
<path d='M 352,16 A 16,16 0 0,0 336,32' fill='none' stroke='currentColor'></path>
<path d='M 104,80 A 16,16 0 0,0 88,96' fill='none' stroke='currentColor'></path>
<path d='M 184,80 A 16,16 0 0,1 200,96' fill='none' stroke='currentColor'></path>
<path d='M 88,96 A 16,16 0 0,0 104,112' fill='none' stroke='currentColor'></path>
<path d='M 200,96 A 16,16 0 0,1 184,112' fill='none' stroke='currentColor'></path>
<path d='M 48,160 A 16,16 0 0,0 32,176' fill='none' stroke='currentColor'></path>
<path d='M 104,160 A 16,16 0 0,1 120,176' fill='none' stroke='currentColor'></path>
<path d='M 176,160 A 16,16 0 0,0 160,176' fill='none' stroke='currentColor'></path>
<path d='M 240,160 A 16,16 0 0,1 256,176' fill='none' stroke='currentColor'></path>
<path d='M 336,160 A 16,16 0 0,0 352,176' fill='none' stroke='currentColor'></path>
<path d='M 32,176 A 16,16 0 0,0 48,192' fill='none' stroke='currentColor'></path>
<path d='M 120,176 A 16,16 0 0,1 104,192' fill='none' stroke='currentColor'></path>
<path d='M 160,176 A 16,16 0 0,0 176,192' fill='none' stroke='currentColor'></path>
<path d='M 256,176 A 16,16 0 0,1 240,192' fill='none' stroke='currentColor'></path>
<text text-anchor='middle' x='48' y='180' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='56' y='180' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='64' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='180' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='80' y='180' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='88' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='96' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='180' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='136' y='100' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='144' y='100' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='152' y='100' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='176' y='180' fill='currentColor' style='font-size:1em'>I</text>
<text text-anchor='middle' x='184' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='184' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='192' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='192' y='180' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='200' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='200' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='216' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='224' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='232' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='240' y='180' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='304' y='84' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='312' y='84' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='320' y='84' fill='currentColor' style='font-size:1em'>H</text>
<text text-anchor='middle' x='400' y='20' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='400' y='100' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='400' y='180' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='408' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='408' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='408' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='416' y='20' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='416' y='100' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='416' y='180' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='424' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='424' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='424' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='432' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='432' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='432' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='440' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='440' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='440' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='456' y='20' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='456' y='100' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='456' y='180' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='464' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='464' y='100' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='464' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='472' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='472' y='100' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='472' y='180' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='480' y='20' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='480' y='100' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='480' y='180' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='488' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='488' y='100' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='488' y='180' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='496' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='496' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='496' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='504' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='504' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='504' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='520' y='20' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='520' y='100' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='520' y='180' fill='currentColor' style='font-size:1em'>3</text>
</g>

    </svg>
  
</div>
<p>To do this, I used <a href="https://www.ansible.com/" title="Ansible" target="_blank" rel="nofollow noopener">Ansible</a><sup id="fnref:1"><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Ansible is a software package,
which runs commands through SSH. The idea is that you don’t define what you want to <em>happen</em>, but
how you want things to <em>be</em>. For example, you don’t ‘start a service’, you make sure ‘the service is
running’.</p>
<p>One huge advantage of this approach is that Ansible Playbooks are idempotent; you can run them as
often as you like, and the system will end up in the desired state every time. It’s not
<em>technically</em> idempotent when you include things like ‘upgrade package x to the latest version’,
since technically a newer version could be available and that would be a different outcome. But that
is only a matter of definitions. Version pinning is available if you want to be strict about that.</p>
<h2 id="ansible" class="link-owner">
  Ansible
</h2><p>Ansible uses ‘playbooks’ in <a href="https://en.wikipedia.org/wiki/YAML" title="YAML notation" target="_blank" rel="nofollow noopener">YAML notation</a>, which contain the
‘tasks’. These playbooks can be subdivided into ‘roles’ for maintainability and readability.
Finally, there’s the inventory, where you define the hosts you want to execute the playbooks on.</p>
<p>A playbook looks like this:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-yaml not-prose" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Update web servers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hosts</span><span class="p">:</span><span class="w"> </span><span class="l">webservers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">remote_user</span><span class="p">:</span><span class="w"> </span><span class="l">root</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tasks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Ensure Apache is at the latest version</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ansible.builtin.yum</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">httpd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l">latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Write the Apache config file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ansible.builtin.template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">src</span><span class="p">:</span><span class="w"> </span><span class="l">/srv/httpd.j2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="l">/etc/httpd.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Ensure Apache is running</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ansible.builtin.service</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">httpd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l">started</span></span></span></code></pre>
  </div>
  


<p>This would execute the steps detailed in the <code>tasks</code> section; update <code>apache</code> (<code>httpd</code>) using <code>yum</code>,
make sure the configuration file is a certain way by using a <code>.j2</code> template<sup id="fnref:2"><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, and finally
make sure the service is running.</p>
<p>Ansible offers a number of tools to make larger projects manageable:</p>
<h3 id="roles" class="link-owner">
  Roles
</h3><p>A role—which is a bit of an unfortunate name—is a self-contained ‘module’<sup id="fnref:3"><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>, it contains
everything you could need, enabling you to group tasks together.</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-yaml not-prose" data-lang="yaml"><span class="line"><span class="cl"><span class="l">roles/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="l">server/              </span><span class="w"> </span><span class="c"># This hierarchy represents a “role”</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">tasks/           </span><span class="w"> </span><span class="c">#</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="l">main.yml     </span><span class="w"> </span><span class="c"># ← Task entrypoint, these get executed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="l">httpd.yml    </span><span class="w"> </span><span class="c"># ← Subtask included by main.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">handlers/        </span><span class="w"> </span><span class="c">#</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="l">main.yml     </span><span class="w"> </span><span class="c"># ← Handlers file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">templates/       </span><span class="w"> </span><span class="c"># ← Files for use with the template module</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="l">ntp.conf.j2  </span><span class="w"> </span><span class="c">#</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">files/           </span><span class="w"> </span><span class="c">#</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="l">bar.txt      </span><span class="w"> </span><span class="c"># ← Files for use with the copy module</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="l">foo.sh       </span><span class="w"> </span><span class="c"># ← Scripts for use with the script module</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="l">vars/            </span><span class="w"> </span><span class="c">#</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="l">main.yml     </span><span class="w"> </span><span class="c"># ← Variables associated with this role</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="l">plesk/               </span><span class="w"> </span><span class="c"># ↖︎</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="l">monitoring/          </span><span class="w"> </span><span class="c"># ← Other roles</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="l">some-app/            </span><span class="w"> </span><span class="c"># ↙︎</span></span></span></code></pre>
  </div>
  
    
    
    <div class="highlight-caption">A directory structure example. Some lesser used subdirectories ommitted.</div>
  


<p>In this project, I set up two roles; one to deal with configuring the server itself, and another to
configure Plesk.</p>
<h3 id="tags" class="link-owner">
  Tags
</h3><p>It is not uncommon to want to run specific tasks without having to execute all tasks in that
playbook, which will take increasingly longer the more the playbook grows. For this, there are
<a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_tags.html" title="tags" target="_blank" rel="nofollow noopener">tags</a>. For example, if
you want to update SSH related settings like <code>authorized_keys</code>, you can run use
<code>ansible-playbook --tag=ssh playbook.yml</code>. It will then run <code>playbook.yml</code>, but only execute the
tasks that have <code>tags: ssh</code>.</p>
<h3 id="handlers" class="link-owner">
  Handlers
</h3><p>There are several tasks that could require a service to be restarted, or a script to be run. It’s
not uncommon to have multiple tasks requiring the same action.</p>
<p>Rather than creating a task to reload a service after each task that requires it, you can use a
<a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_handlers.html" title="handler" target="_blank" rel="nofollow noopener">handler</a>. You can
define these in the <code>handlers</code> block in the same file, or in <code>handlers/main.yml</code> of the same role.</p>
<p>Handlers run after all tasks in the play have been executed. To use our example from earlier:</p>

  

  
  
  
    
  
  

  <div class="highlight has-line-highlights" title="">
    <pre class="chroma" tabindex="0"><code class="language-yaml not-prose" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Update web servers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hosts</span><span class="p">:</span><span class="w"> </span><span class="l">webservers</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">remote_user</span><span class="p">:</span><span class="w"> </span><span class="l">root</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tasks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Write the Apache config file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ansible.builtin.template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">src</span><span class="p">:</span><span class="w"> </span><span class="l">/srv/httpd.j2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="l">/etc/httpd.conf</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span><span class="nt">notify</span><span class="p">:</span><span class="w"> </span><span class="l">Restart Apache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">handlers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Restart Apache</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span><span class="nt">service</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">httpd</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l">restarted</span></span></span></code></pre>
  </div>
  


<h3 id="facts" class="link-owner">
  Facts
</h3><p>Sometimes you need information from the server to decide which actions to take. A great example is
managing which Plesk extensions should be installed and/or removed. On a Plesk server, you’d run
<code>plesk bin extension --list</code>. One way to make the output of that command available in Ansible is to
<a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#registering-variables" title="register a variable" target="_blank" rel="nofollow noopener">register a variable</a>.</p>
<p>However, this isn’t always the best solution. When you need to parse the output, or simply when
there are multiple pieces of information you need from the server, using registered variables can
get unwieldy. Instead, you can use
<a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#registering-variables" title="custom facts" target="_blank" rel="nofollow noopener">custom facts</a>.</p>
<p>Facts are simply files with a <code>.fact</code> extension in <code>/etc/ansible/facts.d/</code> that can be JSON, INI or
executable files returning JSON. For example. You can create a file
<code>/etc/ansible/facts.d/preferences.json</code>:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-json not-prose" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;general&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;foo&#34;</span><span class="p">:</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;bar&#34;</span><span class="p">:</span> <span class="s2">&#34;2&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  


<p>This would then be accessible from within a task like so:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-jinja not-prose" data-lang="jinja"><span class="line"><span class="cl"><span class="cp">{{</span> <span class="nv">ansible_local</span><span class="o">[</span><span class="s1">&#39;preferences&#39;</span><span class="o">][</span><span class="s1">&#39;general&#39;</span><span class="o">][</span><span class="s1">&#39;foo&#39;</span><span class="o">]</span> <span class="cp">}}</span></span></span></code></pre>
  </div>
  


<p>But to manage which Plesk extensions are installed, we need facts that are dynamically generated
each time we run the playbook. To do that, we need an executable file that returns JSON. I chose to
write an easily extendable, self-executable Python file:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-python not-prose" data-lang="python"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/python3</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_installed_extensions</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">stdout</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">check_output</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="s2">&#34;sudo&#34;</span><span class="p">,</span> <span class="s2">&#34;plesk&#34;</span><span class="p">,</span> <span class="s2">&#34;bin&#34;</span><span class="p">,</span> <span class="s2">&#34;extension&#34;</span><span class="p">,</span> <span class="s2">&#34;--list&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">DEVNULL</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">stdout</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="nb">dict</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">installed_extensions</span><span class="o">=</span><span class="n">get_installed_extensions</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl"><span class="p">)))</span></span></span></code></pre>
  </div>
  
    
    
    <div class="highlight-caption">/etc/ansible/facts.d/plesk.fact</div>
  


<p>Whenever you run a playbook on a server, retrieving these facts is one of the first things that
happen, before any other tasks are executed. The facts are accessible in the playbook as
<code>ansible_local.plesk</code>, since we named our file <code>plesk.fact</code>. Now, in our playbook, we can use it
like this:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-yaml not-prose" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Uninstall Advisor extension</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">plesk bin extension --uninstall advisor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;&#34;advisor - Advisor&#34; in ansible_local.plesk.installed_extensions&#39;</span></span></span></code></pre>
  </div>
  


<h2 id="final-thoughts" class="link-owner">
  Final thoughts
</h2><p>There are a number of features that are very useful that I haven’t even covered, like
<a href="https://docs.ansible.com/ansible/latest/vault_guide/index.html" title="Ansible Vault" target="_blank" rel="nofollow noopener">Ansible Vault</a> to manage secrets
and sensitive information, grouping servers and defining variables on a server-level using the
<a href="https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html" title="inventory" target="_blank" rel="nofollow noopener">inventory</a>, the
multitude of available
<a href="https://docs.ansible.com/ansible/latest/collections/index_module.html" title="modules" target="_blank" rel="nofollow noopener">modules</a>,
<a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_debugger.html" title="debugging" target="_blank" rel="nofollow noopener">debugging</a>, and
whatever else I forgot I even used.</p>
<p>Ansible enabled me to provision and manage around ten servers, update their configurations, made it
easy to add more servers when needed, and ultimately enabled me to put the infrastructure in place
that at the time of writing hosts close to a thousand websites.</p>
<p>I’m not a huge fan of YAML, as there are quite a few footguns<sup id="fnref:4"><a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>, especially when it comes to
strings, and has an unintuitive syntax. Unfortunately, it’s an industry standard that is here to
stay. And to be frank, it’s worth dealing with it to be able to use Ansible.</p>
<p>Ansible is versatile, has modules for almost everything you can think of, and enables you to
organize your code and to run only the tasks you need at that moment. The only downside I can think
of is that the execution speed scales poorly once the amount of tasks and servers start to grow.
Running the entire playbook on all of our servers quickly started going towards twenty minutes.</p>
<p>I have very few problems with Ansible, and it enabled me to do a lot on my own. Because of that, I
would definitely recommend looking into it if there’s anything you could use it for.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>While there are alternatives like <a href="https://www.puppet.com/" title="Puppet" target="_blank" rel="nofollow noopener">Puppet</a>,
<a href="https://www.chef.io/" title="Chef" target="_blank" rel="nofollow noopener">Chef</a>, and <a href="https://saltproject.io/" title="Salt" target="_blank" rel="nofollow noopener">Salt</a>, I chose Ansible because of
the bar to entry, how simple the agentless architecture is (it requires no additional software
on the target machines), and how many ‘modules’ it supports—both out of the box and through
third parties. After having used Ansible for a while, there were no major gripes that made me
want to take a more serious look at the alternatives.&#160;<a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_templating.html" title="Jinja templates" target="_blank" rel="nofollow noopener">Jinja templates</a>&#160;<a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>And a ‘module’ is the actual ‘task’ that is being executed on the server. A ‘task’ in Ansible
terms is a module with a specific configuration. Get it yet?</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-yaml not-prose" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Ensure Apache is at the latest version</span><span class="w"> </span><span class="c">#           ← Task</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ansible.builtin.yum</span><span class="p">:</span><span class="w">                         </span><span class="c"># ← Module ← Task</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">httpd                               </span><span class="w"> </span><span class="c">#           ← Task</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l">latest                             </span><span class="w"> </span><span class="c">#           ← Task</span></span></span></code></pre>
  </div>
  


<p>At least there’s a
<a href="https://docs.ansible.com/ansible/latest/reference_appendices/glossary.html" title="glossary" target="_blank" rel="nofollow noopener">glossary</a>
available.&#160;<a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://en.wiktionary.org/wiki/footgun" title="‘footgun’ (plural ‘footguns’)" target="_blank" rel="nofollow noopener">‘footgun’ (plural ‘footguns’)</a>:</p>
<p><em>(<a href="https://en.wiktionary.org/wiki/programming#Noun" title="programming" target="_blank" rel="nofollow noopener">programming</a>
<a href="https://en.wiktionary.org/wiki/Appendix:Glossary#slang" title="slang" target="_blank" rel="nofollow noopener">slang</a>,
<a href="https://en.wiktionary.org/wiki/humorous" title="humorous" target="_blank" rel="nofollow noopener">humorous</a>,
<a href="https://en.wiktionary.org/wiki/derogatory" title="derogatory" target="_blank" rel="nofollow noopener">derogatory</a>)</em> Any
<a href="https://en.wiktionary.org/wiki/feature" title="feature" target="_blank" rel="nofollow noopener">feature</a> whose addition to a product results in the
user <a href="https://en.wiktionary.org/wiki/shoot_oneself_in_the_foot" title="shooting themself in the foot" target="_blank" rel="nofollow noopener">shooting themself in the foot</a>.&#160;<a href="https://hansvl.nl/blog/provisioning-plesk-using-ansible/#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

              ]]></description></item><item><title>Recursively generating heading numbers</title><link>https://hansvl.nl/blog/laravel-recursive-heading-numbers/</link><pubDate>Tue, 22 Aug 2023 17:26:17 +0200</pubDate><lastBuildDate>Wed, 09 Apr 2025 14:45:53 +0200</lastBuildDate><author>Hans van Luttikhuizen-Ross</author><description><![CDATA[ <p><img align="left" hspace="5" style="margin-bottom: 1rem;" src="https://hansvl.nl/blog/laravel-recursive-heading-numbers/organisation-page_cover_hu_fdc955cb795b7905.png"/></p><p>For a project I needed to create a page that would show organizational information. Each item is
either a root item or a child item. The child items can have child items themselves. This is a
recursive structure, which meant I could easily generate header numbers and depth.</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-php not-prose" data-lang="php"><span class="line"><span class="cl"><span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd"> * The heading number including super-items. E.g.: &#34;1.1&#34;.
</span></span></span><span class="line"><span class="cl"><span class="sd"> *
</span></span></span><span class="line"><span class="cl"><span class="sd"> * @return Attribute&lt;string, void&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd"> */</span>
</span></span><span class="line"><span class="cl"><span class="k">protected</span> <span class="k">function</span> <span class="nf">headingNumber</span><span class="p">()</span><span class="o">:</span> <span class="nx">Attribute</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">Attribute</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="nx">fn</span><span class="p">()</span><span class="o">:</span> <span class="nx">string</span> <span class="o">=&gt;</span> <span class="o">!</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">parent</span>
</span></span><span class="line"><span class="cl">        <span class="o">?</span> <span class="s2">&#34;</span><span class="si">$this-&gt;sort_index</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="o">:</span> <span class="s2">&#34;</span><span class="si">{</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">parent</span><span class="o">-&gt;</span><span class="na">heading_number</span><span class="si">}</span><span class="s2">.</span><span class="si">{</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">sort_index</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="sd">/**
</span></span></span><span class="line"><span class="cl"><span class="sd"> * How many levels this item is, starting at 1.
</span></span></span><span class="line"><span class="cl"><span class="sd"> *
</span></span></span><span class="line"><span class="cl"><span class="sd"> * @return Attribute&lt;int, void&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd"> */</span>
</span></span><span class="line"><span class="cl"><span class="k">protected</span> <span class="k">function</span> <span class="nf">depth</span><span class="p">()</span><span class="o">:</span> <span class="nx">Attribute</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">Attribute</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="nx">fn</span><span class="p">()</span><span class="o">:</span> <span class="nx">int</span> <span class="o">=&gt;</span> <span class="mi">1</span> <span class="o">+</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="na">parent</span><span class="o">?-&gt;</span><span class="na">depth</span> <span class="o">??</span> <span class="mi">0</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
  </div>
  


<p>This makes use of Laravel’s
<a href="https://laravel.com/docs/10.x/eloquent-mutators#defining-an-accessor" title="attribute accessors" target="_blank" rel="nofollow noopener">attribute accessors</a> to
create computed properties, which can then be accessed like any other property.</p>
<h2 id="reflection" class="link-owner">
  Reflection
</h2><p>A WYSIWYG<sup id="fnref:1"><a href="https://hansvl.nl/blog/laravel-recursive-heading-numbers/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> editor like is another option. The downside of that is that if you want to make
any change to the structure of the page, you are going to have to manually re-number all the
headings, as I don’t know of any WYSIWYG editor libraries that support auto numbering of headings.
On top of that, it seems like overkill to use a WYSIWYG editor for this.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>What You See Is What You Get&#160;<a href="https://hansvl.nl/blog/laravel-recursive-heading-numbers/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

              ]]></description></item><item><title>Fish function for WebP</title><link>https://hansvl.nl/blog/fish-function-for-webp/</link><pubDate>Sat, 19 Aug 2023 10:44:36 +0200</pubDate><lastBuildDate>Mon, 21 Aug 2023 16:56:41 +0200</lastBuildDate><author>Hans van Luttikhuizen-Ross</author><description><![CDATA[ <p><img align="left" hspace="5" style="margin-bottom: 1rem;" src="https://hansvl.nl/blog/fish-function-for-webp/webp-logo_cover_hu_48dd92eb86556cc8.png"/></p><p>When creating this blog I like to convert my images to WebP, as they save a lot of space at little to no quality loss.
For example, the cover image of this post is 87 KB in PNG format, but only 27 KB in WebP format. That’s a 69%
reduction. Nice.</p>
<p>I use the <a href="https://developers.google.com/speed/webp/docs/cwebp" title="cwebp command line tool" target="_blank" rel="nofollow noopener"><code>cwebp</code> command line tool</a> to convert my images, but
there’s something I really don’t like about it. It always requires you to explicitly specify the output file name.
This makes it so you can&rsquo;t just do something like <code>cwebp *.png</code>, as it will just dump the output to <code>stdout</code>. Not very
helpful.</p>
<p>As I’m using the <a href="https://fishshell.com" title="fish shell" target="_blank" rel="nofollow noopener"><code>fish</code> shell</a> I decided to write a function to make this a little easier:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-fish not-prose" data-lang="fish"><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">convert</span>-to-webp <span class="na">--description</span><span class="o">=</span><span class="s1">&#39;Convert given files to webp format&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="nv">filename</span> <span class="k">in</span> <span class="nv">$argv</span>
</span></span><span class="line"><span class="cl">    <span class="k">set</span> <span class="na">-l</span> <span class="nv">output_filename</span> <span class="o">(</span><span class="nf">path</span> change-extension .webp <span class="nv">$filename</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">cwebp</span> <span class="na">-m</span> <span class="m">6</span> <span class="na">-q</span> <span class="m">70</span> <span class="na">-mt</span> <span class="na">-af</span> <span class="na">-progress</span> <span class="nv">$filename</span> <span class="na">-o</span> <span class="nv">$output_filename</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre>
  </div>
  


<p>Just chuck it into <code>~/.config/fish/functions/convert-to-webp.fish</code><sup id="fnref:1"><a href="https://hansvl.nl/blog/fish-function-for-webp/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> and you can immediately
use <code>convert-to-webp *.png</code> because of <code>fish</code>
’s <a href="https://fishshell.com/docs/current/language.html#autoloading-functions" title="function autoloading" target="_blank" rel="nofollow noopener">function autoloading</a>.</p>
<p>I’m especially fond of <code>path change-extension .webp $filename</code>. It’s a very readable way to change the extension of a
filename. The surrounding parentheses (used
for <a href="https://fishshell.com/docs/current/language.html#command-substitution" title="command substitution in fish" target="_blank" rel="nofollow noopener">command substitution in <code>fish</code></a>) are not
required, but do improve readability.</p>
<p>I’ll leave implementing this in <code>bash</code>/<code>zsh</code> as an exercise to the reader.</p>
<blockquote>
<p><strong>Note:</strong> It turns out this is not needed for this project, as my current Hugo setup automatically converts images to
WebP.</p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The file name doesn’t matter here, as the function name determines how you call it, but it’s good practice to give
them the same name.&#160;<a href="https://hansvl.nl/blog/fish-function-for-webp/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

              ]]></description></item><item><title>B.O.C.K. Part 2: Reverse engineering Egg, Inc.</title><link>https://hansvl.nl/blog/bock-part-2-reverse-engineering-egg-inc/</link><pubDate>Fri, 18 Aug 2023 13:36:40 +0200</pubDate><author>Hans van Luttikhuizen-Ross</author><description><![CDATA[ <p><img align="left" hspace="5" style="margin-bottom: 1rem;" src="https://hansvl.nl/blog/bock-part-2-reverse-engineering-egg-inc/protobuf-logo_cover_hu_5fdb7a3a6e6792d4.png"/></p><p>To make a Discord bot to help organize co-op teams in Egg, Inc., I needed to get data from the game.
There are no documented APIs, so I was going to have to reverse engineer the game’s network traffic.</p>
<h2 id="charles-proxy" class="link-owner">
  Charles Proxy
</h2><p><a href="https://www.charlesproxy.com/" title="Charles Proxy" target="_blank" rel="nofollow noopener">Charles Proxy</a> allows you to intercept network traffic. You can even
use it to
<a href="https://www.charlesproxy.com/documentation/using-charles/ssl-certificates/" title="intercept encrypted HTTPS traffic from your iPhone" target="_blank" rel="nofollow noopener">intercept encrypted HTTPS traffic from your iPhone</a>.
Perfect! So I set up my iPhone to use Charles Proxy as a proxy server, and started the game.</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/bock-part-2-reverse-engineering-egg-inc/charles-proxy.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/charles-proxy_hu_941172162187c09b.webp" />
          <source media="(max-width: 767px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/charles-proxy_hu_58d9693d6dde74e.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/charles-proxy_hu_c2ee7d63cda4de4a.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/charles-proxy_hu_7eff148370e5e54c.webp" />
          <img
            class="rounded-lg mx-auto"
            title="Charles Proxy screenshot"
            alt="Charles Proxy showing network traffic from Egg, Inc."
            loading="lazy"
            src="/blog/bock-part-2-reverse-engineering-egg-inc/charles-proxy.png"
            
              style=" background: linear-gradient(-30deg, #343434,
              #959698); background-size: cover; "
            
            width="1716"
            height="1756"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        Charles Proxy showing network traffic from Egg, Inc.
      </figcaption>
    </figure>
  


</p>
<p>Whenever I opened the game, I would see a number of requests and responses. The response to
<code>/ei/first_contact</code> had almost 100kb of Base64-encoded data. The rest of the traffic didn’t look
half as interesting, and when I started there was probably not even half of what is shown in the
screenshot.</p>
<p>After decoding, it was still mostly unintelligible, but slightly less so. It was in a binary format,
so I grabbed a hex editor and started looking for patterns.</p>
<h2 id="protobuf" class="link-owner">
  Protobuf
</h2><p>As you can see in the image below, there are some repeating patterns. At the start of the file I
could see my in-game name, and my iCloud ID. I was on the right track.</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/bock-part-2-reverse-engineering-egg-inc/first-contact-hex.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/first-contact-hex_hu_4b01c986fe4502a.webp" />
          <source media="(max-width: 767px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/first-contact-hex_hu_18db004ebea72303.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/first-contact-hex_hu_f9b42e242dbbadd.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/first-contact-hex_hu_427cc920beb79501.webp" />
          <img
            class="rounded-lg mx-auto"
            title="Hex Fiend screenshot"
            alt="Hex Fiend showing binary and text representations of the response data, revealing repeating patterns."
            loading="lazy"
            src="/blog/bock-part-2-reverse-engineering-egg-inc/first-contact-hex.png"
            
              style=" background: linear-gradient(-30deg, #404040,
              #909090); background-size: cover; "
            
            width="1194"
            height="752"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        Hex Fiend showing binary and text representations of the response data, revealing repeating patterns.
      </figcaption>
    </figure>
  


</p>
<p>There was a lot of data, but I couldn’t make sense of most of it. I had seen APIs use JSON or XML,
but I’d never seen anything like this before. And since I did see my name and iCloud ID, I knew I
wasn’t looking at encrypted data.</p>
<p>After a little bit of research, I figured out that the data was probably serialized using
<a href="https://protobuf.dev" title="Protocol Buffers" target="_blank" rel="nofollow noopener">Protocol Buffers</a>. This is a binary format for serializing structured data.
It competes with the likes of JSON and XML, but is much more compact and faster to parse. However,
it is not human-readable.</p>
<p>To make sense it you need a Protocol buffer definitions file. These <code>.proto</code> files contain the
structure of the data, called <em>messages</em>. They define the names and types of the data, which a
program uses to encode outgoing and decode incoming messages. For example, a message could be
defined as follows:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-protobuf not-prose" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">User</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">required</span> <span class="kt">int32</span> <span class="n">id</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">required</span> <span class="kt">string</span> <span class="n">name</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="n">Post</span> <span class="n">posts</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="kd">message</span> <span class="nc">Post</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">required</span> <span class="kt">string</span> <span class="n">title</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">required</span> <span class="kt">string</span> <span class="n">content</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span></span></span></code></pre>
  </div>
  


<p>Without a definitions file, you only have two pieces of information: a sequence number and the raw
bytes of data. You don’t even know how to interpret the data; whether it’s a <code>string</code>, an <code>int32</code> or
something else. It’s the sequence number that links the data to the definitions file and tells the
program how to interpret it. This is the purpose of the <code>= 1</code> and <code>= 2</code> in the example above.</p>
<h2 id="reverse-engineering" class="link-owner">
  Reverse engineering
</h2><p>To get the data in a state I could work with, I started the process of reverse engineering the
Protocol buffer definitions file. First, I started by looking for patterns in the response data. I
noticed that there were a lot of bytes that were appearing in a certain cadence.</p>
<p>There were a lot of blank chunks, just bytes of zeroes, intermingled with consistent structures with
repeating elements, where I had also noticed a number that increased by one each time. This is our
sequence number!</p>
<p>I can’t remember how I figured out the message boundaries and types, but this is a snippet of the
first iteration of my <code>egginc.proto</code> file:</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-protobuf not-prose" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">msg7</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">int32</span> <span class="n">unknown1</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// 17 for me, highest egg reached or something?
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kt">uint64</span> <span class="n">ge_total</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">ge_spent</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint64</span> <span class="n">soul_eggs</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">prestige_earnings</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">lifetime_earnings</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">piggy_bank</span> <span class="o">=</span> <span class="mi">7</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">unknown8</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="n">name_level</span> <span class="n">epic_research</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">unknown10</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span> <span class="c1">// looks like a timestamp
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="k">repeated</span> <span class="n">name_level</span> <span class="n">newspaper</span> <span class="o">=</span> <span class="mi">11</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">unknown12</span> <span class="o">=</span> <span class="mi">12</span><span class="p">;</span> <span class="c1">// looks like a timestamp
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kt">double</span> <span class="n">unknown13</span> <span class="o">=</span> <span class="mi">13</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">video_expire_time</span> <span class="o">=</span> <span class="mi">14</span><span class="p">;</span> <span class="c1">// looks like a timestamp
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="k">repeated</span> <span class="n">name_level</span> <span class="n">achievement</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">unknown16</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">unknown17</span> <span class="o">=</span> <span class="mi">17</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="kt">uint32</span> <span class="n">unknown18</span> <span class="o">=</span> <span class="mi">18</span><span class="p">;</span> <span class="c1">// &#34;highest amount of chickens obtained on that egg&#34;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="k">repeated</span> <span class="kt">uint32</span> <span class="n">unknown19</span> <span class="o">=</span> <span class="mi">19</span><span class="p">;</span> <span class="c1">// &#34;level of trophy obtained for their respective eggs&#34;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kt">uint32</span> <span class="n">unknown20</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span> <span class="c1">// related to daily gift &#34;day&#34;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kt">uint64</span> <span class="n">unknown23</span> <span class="o">=</span> <span class="mi">23</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">unknown24</span> <span class="o">=</span> <span class="mi">24</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">unknown25</span> <span class="o">=</span> <span class="mi">25</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">unknown26</span> <span class="o">=</span> <span class="mi">26</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">unknown27</span> <span class="o">=</span> <span class="mi">27</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span></span></span></code></pre>
  </div>
  


<p>After a while I realized that I wasn’t going to be able to wade through almost 100,000 bytes of raw
binary data by hand. I would have to find a way to get a Protobuf definitions file somehow.</p>
<p>Any application that serializes and deserializes Protobuf messages needs to have the definition of
the messages somewhere. Egg, Inc. is available on both the
<a href="https://apps.apple.com/us/app/egg-inc/id993492744" title="App Store" target="_blank" rel="nofollow noopener">App Store</a> and the
<a href="https://play.google.com/store/apps/details?id=com.auxbrain.egginc" title="Google Play Store" target="_blank" rel="nofollow noopener">Google Play Store</a>. This means
that I could look for the Android app’s APK<sup id="fnref:1"><a href="https://hansvl.nl/blog/bock-part-2-reverse-engineering-egg-inc/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> file, which I downloaded and extracted.</p>
<p>Unfortunately, there was no ready-made <code>egginc.proto</code> file to be found. The definitions <em>had</em> to be
there somewhere, so I was going to have to dig a little deeper. There were a few compiled Android
Library (<code>.so</code>) files that looked interesting, so I grabbed my hex editor again and loaded up those
files.</p>
<p>






  

  

  
    
    
    
    
    


    <figure>
      <a href="https://hansvl.nl/blog/bock-part-2-reverse-engineering-egg-inc/libegginc-hex.png">
        <picture>
          <source media="(max-width: 639px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/libegginc-hex_hu_97651d36ceb7d80b.webp" />
          <source media="(max-width: 767px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/libegginc-hex_hu_1cc711f0cadad93d.webp" />
          <source media="(max-width: 1023px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/libegginc-hex_hu_f31ed95d8ac6c7ad.webp" />
          <source media="(min-width: 1024px)" srcset="/blog/bock-part-2-reverse-engineering-egg-inc/libegginc-hex_hu_7075019eb09ac834.webp" />
          <img
            class="rounded-lg mx-auto"
            title="Hex Fiend"
            alt="Hex Fiend showing binary and text representations of compiled Protobuf definitions in the `libegginc.so` binary data."
            loading="lazy"
            src="/blog/bock-part-2-reverse-engineering-egg-inc/libegginc-hex.png"
            
              style=" background: linear-gradient(-30deg, #434343,
              #8f8f8f); background-size: cover; "
            
            width="1194"
            height="752"
          />
        </picture>
      </a>
      <figcaption class="text-sm italic">
        Hex Fiend showing binary and text representations of compiled Protobuf definitions in the <code>libegginc.so</code> binary data.
      </figcaption>
    </figure>
  


</p>
<p>When looking through <code>libegginc.so</code>, I saw the string <code>ei.proto</code> (highlighted in the image above). I
struck gold! Everything was here! Message names, property names, types (represented by a number),
everything!</p>
<p>With some good old Regex I wrote
<a href="https://github.com/pindab0ter/EggBot/blob/9feb33358f31483a292383ba4903fb114d94343a/src/main/kotlin/nl/pindab0ter/eggbot/utilities/ProtoExtractor.kt" title="a tool" target="_blank" rel="nofollow noopener">a tool</a><sup id="fnref:2"><a href="https://hansvl.nl/blog/bock-part-2-reverse-engineering-egg-inc/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>
to help me parse the data.</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-kotlin not-prose" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">const</span> <span class="k">val</span> <span class="py">START</span> <span class="p">=</span> <span class="s2">&#34;ei.proto</span><span class="se">\u0012\u0002</span><span class="s2">ei</span><span class="se">\u001A\u000C</span><span class="s2">common.proto&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="k">val</span> <span class="py">END</span> <span class="p">=</span> <span class="s2">&#34;</span><span class="se">\u0000\u0000\u0000\u0000</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">val</span> <span class="py">componentPattern</span> <span class="p">=</span> <span class="s">&#34;&#34;&#34;\x0A.{0,2}?(?&lt;name&gt;[A-Za-z]+)(?&lt;body&gt;.*?)(?=\x0A.[A-Z][a-z]|$)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">toRegex</span><span class="p">(</span><span class="n">DOT_MATCHES_ALL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">val</span> <span class="py">classPattern</span> <span class="p">=</span> <span class="s">&#34;&#34;&#34;[a-z\d_]{3,}&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">toRegex</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">val</span> <span class="py">propertyPattern</span> <span class="p">=</span> <span class="s">&#34;&#34;&#34;\x0A.(?&lt;name&gt;\w+).(?&lt;index&gt;.).*?(?&lt;repeated&gt;.)\x28(?&lt;primitive&gt;.)(?:\x32.\..+?\.(?&lt;reference&gt;[A-Za-z.]*))?&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">toRegex</span><span class="p">(</span><span class="n">DOT_MATCHES_ALL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">val</span> <span class="py">constantPattern</span> <span class="p">=</span> <span class="s">&#34;&#34;&#34;(?&lt;name&gt;[A-Z_]{2,})\x10(?&lt;index&gt;.).*?(?:\x0A|$)&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">toRegex</span><span class="p">(</span><span class="n">DOT_MATCHES_ALL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// …
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">Int</span><span class="p">.</span><span class="n">toType</span><span class="p">():</span> <span class="n">String</span> <span class="p">=</span> <span class="k">when</span> <span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="m">1</span> <span class="o">-&gt;</span> <span class="s2">&#34;double&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="m">2</span> <span class="o">-&gt;</span> <span class="s2">&#34;float&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="m">5</span> <span class="o">-&gt;</span> <span class="s2">&#34;int32&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="m">4</span> <span class="o">-&gt;</span> <span class="s2">&#34;uint64&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="m">8</span> <span class="o">-&gt;</span> <span class="s2">&#34;bool&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="m">9</span> <span class="o">-&gt;</span> <span class="s2">&#34;string&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="m">13</span> <span class="o">-&gt;</span> <span class="s2">&#34;uint32&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span> <span class="o">-&gt;</span> <span class="s2">&#34;??? (</span><span class="si">$this</span><span class="s2">)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// …</span></span></span></code></pre>
  </div>
  


<p>It took me a lot of time to get the Regex right and to get the types down from the numbers. I also
had to figure out how to parse the repeated elements and where messages started and ended, but I got
there in the end.</p>
<p>The output of this tool much more helpful than my previous attempts at reading the data by hand.</p>

  

  
  
  
  

  <div class="highlight" title="">
    <pre class="chroma" tabindex="0"><code class="language-protobuf not-prose" data-lang="protobuf"><span class="line"><span class="cl"><span class="kd">message</span> <span class="nc">Game</span> <span class="p">{</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="n">Egg</span> <span class="n">max_egg_reached</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint64</span> <span class="n">golden_eggs_earned</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint64</span> <span class="n">golden_eggs_spent</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint64</span> <span class="n">soul_eggs</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">prestige_cash_earned</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">lifetime_cash_earned</span> <span class="o">=</span> <span class="mi">6</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint64</span> <span class="n">piggy_bank</span> <span class="o">=</span> <span class="mi">7</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">permit_level</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="n">Backup.ResearchItem</span> <span class="n">epic_research</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">next_daily_gift_time</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="n">Backup.NewsHeadline</span> <span class="n">news</span> <span class="o">=</span> <span class="mi">11</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">last_news_time</span> <span class="o">=</span> <span class="mi">12</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">current_multiplier</span> <span class="o">=</span> <span class="mi">13</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">current_multiplier_expiration</span> <span class="o">=</span> <span class="mi">14</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="n">Backup.AchievementInfo</span> <span class="n">achievements</span> <span class="o">=</span> <span class="mi">15</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint64</span> <span class="n">uncliamed_golden_eggs</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint64</span> <span class="n">unclaimed_soul_eggs</span> <span class="o">=</span> <span class="mi">17</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="kt">uint64</span> <span class="n">max_farm_size_reached</span> <span class="o">=</span> <span class="mi">18</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="kt">uint32</span> <span class="n">egg_medal_level</span> <span class="o">=</span> <span class="mi">19</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">last_daily_gift_collected_day</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">current_farm</span> <span class="o">=</span> <span class="mi">22</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint64</span> <span class="n">eggs_of_prophecy</span> <span class="o">=</span> <span class="mi">23</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint64</span> <span class="n">unclaimed_eggs_of_prophecy</span> <span class="o">=</span> <span class="mi">24</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">bool</span> <span class="n">long_idle_notification_set</span> <span class="o">=</span> <span class="mi">25</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">long_idle_notification_threshold</span> <span class="o">=</span> <span class="mi">26</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">long_idle_reward</span> <span class="o">=</span> <span class="mi">27</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">num_daily_gifts_collected</span> <span class="o">=</span> <span class="mi">28</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">bool</span> <span class="n">hyperloop_station</span> <span class="o">=</span> <span class="mi">29</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="k">repeated</span> <span class="n">Backup.OwnedBoost</span> <span class="n">boosts</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">bool</span> <span class="n">piggy_full_alert_shown</span> <span class="o">=</span> <span class="mi">31</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">uint32</span> <span class="n">total_time_cheats_detected</span> <span class="o">=</span> <span class="mi">32</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">prestige_soul_boost_cash</span> <span class="o">=</span> <span class="mi">33</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">soul_eggs_d</span> <span class="o">=</span> <span class="mi">34</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">unclaimed_soul_eggs_d</span> <span class="o">=</span> <span class="mi">35</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">bool</span> <span class="n">force_elite_contracts</span> <span class="o">=</span> <span class="mi">36</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span>  <span class="kt">double</span> <span class="n">new_player_event_end_time</span> <span class="o">=</span> <span class="mi">37</span><span class="p">;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="p">}</span></span></span></code></pre>
  </div>
  


<p>As you can see, my suspicion from my first attempt that <code>uint32 unknown1 = 1;</code> represented the
highest egg reached was indeed correct. I even coupled the value of the <code>uint32</code> to the <code>Egg</code> enum
that I also pulled from the library file. It was all coming together.</p>
<h3 id="communicating-with-the-game-servers" class="link-owner">
  Communicating with the game servers
</h3><p>I set up the project so that it automatically generates classes based on the Protocol buffer
definitions and use a library to do the serialization and deserialization for me.</p>
<p>Every time the game launches, it synchronizes the game state with the server. This was the big
response I was seeing early on in Charles. I was able to reverse engineer a <em>request</em> message for
that initial data exchange request, and lo and behold, the server gave me the entire game state of
my Egg, Inc. game!</p>
<p>The next steps were clear: Create a database of player credentials to request those backups, create
a Discord bot for them to interact with, and have them start organizing co-ops with it.</p>
<blockquote>
<p><strong>Note:</strong> The creators of Egg, Inc. have had to deal with cheaters in the past. Security has been
much improved since then.</p>
<p>B.O.C.K. has only ever sent read-only requests. Please be a decent human being and don’t cheat.</p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Android Package Kit, the file format used by Android to distribute and install apps. They are
basically ZIP files containing the app’s code and resources.&#160;<a href="https://hansvl.nl/blog/bock-part-2-reverse-engineering-egg-inc/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>The structure of the APK has changed since writing this tool. Unfortunately, it no longer works
on recent versions of the game.&#160;<a href="https://hansvl.nl/blog/bock-part-2-reverse-engineering-egg-inc/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

              ]]></description></item><item><title>B.O.C.K. Part 1: Introduction</title><link>https://hansvl.nl/blog/bock-part-1-introduction/</link><pubDate>Tue, 15 Aug 2023 18:37:29 +0200</pubDate><lastBuildDate>Sat, 06 Apr 2024 11:12:49 +0200</lastBuildDate><author>Hans van Luttikhuizen-Ross</author><description><![CDATA[ <p><img align="left" hspace="5" style="margin-bottom: 1rem;" src="https://hansvl.nl/blog/bock-part-1-introduction/egg-inc-triptych_cover_hu_7183bead9f4fc7cc.png"/></p><h2 id="egg-inc" class="link-owner">
  Egg, Inc.
</h2><p>Egg, Inc.<sup id="fnref:1"><a href="https://hansvl.nl/blog/bock-part-1-introduction/#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> is an idle game by the lovely people at <a href="https://auxbrain.com/" title="Auxbrain" target="_blank" rel="nofollow noopener">Auxbrain</a>.</p>
<p>The conceit of this game is that you have hatcheries that spawn chickens who lay increasingly more
and valuable eggs that sell for increasingly high prices. The result is a game where numbers are
almost always displayed in orders of magnitude<sup id="fnref:2"><a href="https://hansvl.nl/blog/bock-part-1-introduction/#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. To offer some perspective, my
lifetime earnings in-game sit at a respectable 194.529 quattuorvigintillion<sup id="fnref:3"><a href="https://hansvl.nl/blog/bock-part-1-introduction/#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>
bocks.</p>
<h3 id="idle-games" class="link-owner">
  Idle games
</h3><p>The gameplay loop is simple: you buy upgrade your habitats, hatcheries and transportation vehicles,
and buy ‘research’ that further increase your growth and earnings. You then close the game. All the
counters keep running, so when you come back after a while you will have accumulated a lot of
income, which you can then spend on more growth.</p>
<p>The game is free to play, but offers in-app purchases. I’ve spent some money on it, but nowhere near
the amount I’ve spent on other games. Especially considering the amount of time I’ve spent playing
it.</p>
<h3 id="contracts-and-communities" class="link-owner">
  Contracts and communities
</h3><p>The game offers weekly time-limited contracts that can be completed by joining a co-op with other
players. There are several communities that group people of similar ranking together. I performed
well on a number of those, and this lead to me being invited to a private, invite-only Discord
community in October 2015.</p>
<p>This community would organize co-op centrally using spreadsheets, detailing team captains, multiple
scenarios for different max co-op sizes, Discord channels that would have to be co-ordinated, the
works.</p>
<p>I asked them to <em>please</em> tell me this wasn’t all manual labour. It was.</p>
<p>I offered to automate the process. This blog series is about how I created a Discord bot to help.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://apps.apple.com/us/app/egg-inc/id993492744" title="App Store" target="_blank" rel="nofollow noopener">App Store</a>,
<a href="https://play.google.com/store/apps/details?id=com.auxbrain.egginc" title="Google Play" target="_blank" rel="nofollow noopener">Google Play</a>&#160;<a href="https://hansvl.nl/blog/bock-part-1-introduction/#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Kongregate, makers of several idle games themselves, have done a great series on the math behind
these kind of games:
<a href="https://blog.kongregate.com/the-math-of-idle-games-part-i/" title="The Math of Idle Games, Part I" target="_blank" rel="nofollow noopener">The Math of Idle Games, Part I</a>,
<a href="https://blog.kongregate.com/the-math-of-idle-games-part-ii/" title="Part II" target="_blank" rel="nofollow noopener">Part II</a>,
<a href="https://blog.kongregate.com/the-math-of-idle-games-part-iii/" title="Part III" target="_blank" rel="nofollow noopener">Part III</a>,&#160;<a href="https://hansvl.nl/blog/bock-part-1-introduction/#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>That’s about 194.​529.​000.​000.​000.​000.​000.​000.​000.​000.​000.​000.​000.​000.​000.
​000.​000.​000.​000.​000.​000.​000.​000.​000.​000.​000&#160;<a href="https://hansvl.nl/blog/bock-part-1-introduction/#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>

              ]]></description></item></channel></rss>