<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Ruben Van Assche]]></title><description><![CDATA[Backend developer creating things with PHP, Laravel & more]]></description><link>https://rubenvanassche.com/</link><image><url>https://rubenvanassche.com/favicon.png</url><title>Ruben Van Assche</title><link>https://rubenvanassche.com/</link></image><generator>Ghost 5.39</generator><lastBuildDate>Mon, 06 Apr 2026 19:22:46 GMT</lastBuildDate><atom:link href="https://rubenvanassche.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Hi there, laravel-data 4]]></title><description><![CDATA[One year after version 3, the newest version of laravel-data is packed with features.]]></description><link>https://rubenvanassche.com/hi-there-laravel-data-4/</link><guid isPermaLink="false">65b38dfce2ea0104d622cf2a</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Fri, 09 Feb 2024 08:21:50 GMT</pubDate><content:encoded><![CDATA[<p>We launched laravel-data 3 a bit more then a year ago, and since then, we&apos;ve released 26 versions packed with bug fixes and new features.</p><p>We started working this summer on laravel-data 4 together with typescript-transformer 3 (spoiler: that&apos;s my next priority, laravel-data 4 took longer than expected). After 136 commits, 316 files changed, 9306 lines removed, and a whopping 18143 lines added, it is finally out. Let&apos;s take a look at what&apos;s new!</p><h2 id="a-datacollection-less-world">A DataCollection-less world</h2><p>When you had a collection of data objects nested in a data object, you were always required to use either a <code>DataCollection</code>, <code>PaginatedDataCollection</code>, or <code>CursorPaginatedDataCollection</code> to make sure the package would handle these objects as data objects. There was also a requirement to use the <code>DataCollectionOf</code> attribute to tell what was inside the collection.</p><p>These extra collections made code less readable; sometimes you want to use an array, not a whole collection. The support for extra methods like <code>filter</code>, <code>reduce</code>, ... was a much requested feature but it always felt that laravel-data was not the package that should implement those methods. Also, static analyzers and IDEs weren&apos;t always happy with these collections.</p><p>From now on, you&apos;re free to use <code>array</code>&apos;s, Laravel Collections, and paginators with data objects; the requirement to type what data object it stores is still there. But, an annotation can now be used, which will let your IDE provide much better completions than before:</p><figure class="kg-card kg-code-card"><pre><code class="language-php">class AlbumData extends Data
{
    public function __construct(
        public string $title,
        #[DataCollectionOf(SongData::class)]
        public DataCollection $songs,
    ){
    }
}
</code></pre><figcaption>v3</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-php">class AlbumData extends Data
{
    public function __construct(
        public string $title,
        /** @var SongData[] */
        public array $songs,
    ){
    }
}
</code></pre><figcaption>v4</figcaption></figure><h2 id="the-removal-of-the-collection-method">The removal of the collection method</h2><p>When creating a collection of data objects, you used the <code>collection</code> method in the past. That method has been removed in favor of the <code>collect</code> method to follow the Laravel convention.</p><p>Another reason for a new name is the changed behavior of the <code>collect</code> method concerning <code>collection</code>. The <code>collect</code> method will return the same collection type passed in. So, when passing in an array, you&apos;ll get an array with data objects returned. The <code>collect</code> method always returned either a <code>DataCollection</code>, <code>PaginatedDataCollection</code>, or <code>CursorPaginatedDataCollection</code>.</p><figure class="kg-card kg-code-card"><pre><code class="language-php">SongData::collection($anArray); // DataCollection&lt;SongData&gt;</code></pre><figcaption>v3</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-php">SongData::collect($anArray); // SongData[]</code></pre><figcaption>v4</figcaption></figure><p>If you still require a <code>DataCollection</code>, no problem! You can tell the <code>collect</code> method what to return:</p><pre><code class="language-php">SongData::collect($anArray, DataCollection::class); // DataCollection&lt;SongData&gt;
</code></pre><h2 id="magic-collect">Magic collect</h2><p>We already had magic methods for creating data objects using <code>from</code>. Now, we&apos;re extending this to collections, yet another reason to remove the <code>collection</code> method in favor of the <code>collect</code> method.</p><pre><code class="language-php">class SongData extends Data
{
    public function __construct(
        public string $name,
        public int $duration
    )
    {}
    
    public function collectLaravelCollection(Collection $collection): SongCollection
    {
        return new SongCollection($collection-&gt;all())
    }
}
</code></pre><h2 id="including-the-included-includes">Including, the included, includes</h2><p>Laravel-data already had includes, which allowed you to construct an array from your data object how you liked it to be.</p><p>For example, we can make the <code>songs</code> property of <code>AlbumData</code> lazy:</p><pre><code class="language-php">class AlbumData extends Data
{
    public function __construct(
        public string $title,
        /** @var Lazy|SongData[] */
        public Lazy|array $songs,
    ){
    }
}
</code></pre><p>Transforming an album to an array will not include the <code>songs</code> property. You can add it to the array by manually including it:</p><pre><code class="language-php">$album-&gt;include(&apos;songs&apos;)-&gt;toArray();
</code></pre><p>The whole partials system, which ensures that includes, excludes, only, and except are performed correctly, was rewritten entirely in v3. We used a complex tree-based structure over there, which was complicated to work on, not performant at all, and stopped us from creating new features. My computer science background took over on that system, while the more pragmatic solution was far better.</p><p>The partials system is constantly being used when transforming data objects or collections into arrays and responses, ... it needs to be fast to have a performant application. That&apos;s why we&apos;ve rewritten the whole partials system again! This time, it&apos;s structured much more straightforward, and we&apos;ve ensured it is performant!</p><p>Next to performance, we tried to remove some kinks from the partial system. Previously, when adding a partial to a data object, that partial would sit there forever. The next time you transform an object, the partial will be executed again. In laravel-data 4, this isn&apos;t the case anymore:</p><figure class="kg-card kg-code-card"><pre><code class="language-php">$album-&gt;include(&apos;songs&apos;)-&gt;toArray(); // songs is included 
$album-&gt;toArray(): // songs is included</code></pre><figcaption>v3</figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-php">$album-&gt;include(&apos;songs&apos;)-&gt;toArray(); // songs is included 
$album-&gt;toArray(): // songs is NOT included</code></pre><figcaption>v4</figcaption></figure><p>If you need partials that are always included, then add them to the <code>includedProperties</code> method on your data object or use the new <code>includePermanently</code> method:</p><pre><code class="language-php">$album-&gt;includePermanently(&apos;songs&apos;)-&gt;toArray(); // songs is included
$album-&gt;toArray(): // songs is included
</code></pre><p>Another change to the partials system is allowing inclusions to be defined wherever you want within your chain of data objects.</p><p>If we introduce another object:</p><pre><code class="language-php">class ArtistData extends Data
{
    public function __construct()
    {
        public string $name,
        /** @var Lazy|AlbumData[] */
        public Lazy|array $albums,
    }
}
</code></pre><p>Let&apos;s create the objects by getting them from the database:</p><pre><code class="language-php">$albums = Album::with(&apos;songs&apos;)-&gt;get()-&gt;map(function(Album $album){
    $albumData = AlbumData::from($album);
    
    if($albumData-&gt;title === &apos;Whenever You Need Somebody&apos;)
    {
        $albumData-&gt;include(&apos;songs&apos;);
    }
    
    return $albumData;
});

$artist = ArtistData::from([
    &apos;name&apos; =&gt; &apos;Rick Astley&apos;,
    &apos;albums&apos; =&gt; Lazy::create($albums)
]);

$artist-&gt;include(&apos;albums&apos;)-&gt;toArray();
</code></pre><p>As you can see, we include the albums on the root object, then add an include for the songs for one specific album.</p><p>Sidenote: for everyone who read the docs and tests of laravel-data, you know why I want to see the songs of this album ;)</p><p>In the previous versions, only the albums would be included, but not the songs of that specific album. You were always required to add includes only on the root level when transforming a data object into an array.</p><p>Laravel-data and its new partials system finally allow you to add includes wherever you want, meaning that the songs will be transformed into an array for that specific album.</p><h2 id="new-abstract-classes">New abstract classes</h2><p>You must always extend from <code>Data</code> when creating data objects using the package. This class packs a lot of features like validation, transforming to arrays, casting, mapping property names, additional properties, and wrapping data.</p><p>In some situations, you might want to use leaner objects; that&apos;s why we&apos;ve added <code>Dto</code> and <code>Resource.</code></p><p>The <code>Dto</code> class allows you to create DTOs and nothing more; you can&apos;t transform them, they can&apos;t be wrapped, and they have no concept of includes making them ideal for simple objects to be used within your application.</p><p>As for the <code>Resource</code> objects, these mirror the Laravel resources; they allow data objects to be transformed into responses with wrapping, including properties and a lot more. But we&apos;ve stripped the validation logic from them.</p><h2 id="creating-data-objects-by-factory">Creating data objects by factory</h2><p>Each data object now has a new method, <code>factory</code> which allows you to create data objects using a factory pattern and define how the data object will be built.</p><p>It is possible, for example, to disable the mapping of property names and add some additional global casts:</p><pre><code class="language-php">SongData::factory()
    -&gt;withoutPropertyNameMapping()
    -&gt;withCast(&apos;string&apos;, StringToUpperCast::class)
    -&gt;from($song);
</code></pre><p>In the factory, you can also configure whether magical creation is enabled wha, what magical methods to ignore, and whether payloads should be validated.</p><p>This factory creates a <code>CreationContext</code> object, which is then passed to all data, the cats, pipelines, and data objects being constructed.</p><h2 id="transforming-data-by-factory-pattern">Transforming data by factory pattern</h2><p>The transformation process could not stay behind since the creation process can now be controlled with a factory.</p><p>The <code>transformation</code> method was changed to allow passing in a factory. You can now add extra partials, turn off transformation of values, turn off property name mapping, add wrapping, and a lot more:</p><pre><code class="language-php">$artistData-&gt;transform(
    TransformationFactory::create()
        -&gt;incude(&apos;albums.songs&apos;)
        -&gt;withWrapping()
        -&gt;withTransformer(&apos;string&apos;, StringToUpperTransformer::class)
);
</code></pre><p>Kinda like the creation process, this factory will produce a <code>TransformationContext</code>, which will be passed to the transformers when transforming the data object to an array.</p><h2 id="validation-strategies">Validation strategies</h2><p>A much-discussed subject on GitHub was whether all payloads provided to laravel-data should be validated. The default behavior is that requests always will be validated. The <code>validateAndCreate</code> method could be used if you want to validate something else.</p><p>In some situations, you want to be able to validate all the payloads passed to laravel-data. That&apos;s why you can now change the validation strategy to only requests, always, and disabled.</p><p>You can find this setting in the <code>data.php</code> config file or can use a factory for this:</p><pre><code class="language-php">SongData::factory()
    -&gt;alwaysValidate()
    -&gt;from($song);
</code></pre><h2 id="speed">Speed</h2><p>We&apos;ve added some benchmarks using phpbench to check how fast some operations are. If we compare the results of laravel-data v3 and v4, then we can see spectacular results:</p><figure class="kg-card kg-code-card"><pre><code>benchCollectionTransformation...........I4 - Mo673.524&#x3BC;s (&#xB1;1.57%)
benchObjectTransformation...............I4 - Mo49.853&#x3BC;s (&#xB1;0.34%)
benchCollectionCreation.................I4 - Mo2.231ms (&#xB1;0.75%)
benchObjectCreation.....................I4 - Mo149.323&#x3BC;s (&#xB1;0.71%)
</code></pre><figcaption>v3</figcaption></figure><figure class="kg-card kg-code-card"><pre><code>benchCollectionTransformation...........I4 &#x2714; Mo596.342&#x3BC;s (&#xB1;1.23%)
benchObjectTransformation...............I4 &#x2714; Mo39.843&#x3BC;s (&#xB1;0.84%)
benchCollectionCreation.................I4 &#x2714; Mo1.337ms (&#xB1;0.56%)
benchObjectCreation.....................I4 &#x2714; Mo91.171&#x3BC;s (&#xB1;0.81%)
</code></pre><figcaption>v4</figcaption></figure><p>As you can see, the transformation process is now a bit faster (10-15%), but the creation process is 40% faster!</p><p>A quick tip: these benchmarks were run with the structure caching feature we introduced in v3. Without this caching, the results look like this:</p><pre><code>benchCollectionTransformationWithoutCac.I4 &#x2714; Mo797.039&#x3BC;s (&#xB1;2.93%)
benchObjectTransformationWithoutCache...I4 &#x2714; Mo233.757&#x3BC;s (&#xB1;1.72%)
benchCollectionCreationWithoutCache.....I4 &#x2714; Mo1.627ms (&#xB1;1.45%)
benchObjectCreationWithoutCache.........I4 &#x2714; Mo351.498&#x3BC;s (&#xB1;0.72%)
</code></pre><p>Always cache your structures when deploying to production!</p><h2 id="a-revamped-type-system">A revamped type system</h2><p>Laravel-data packs a lot of magic to create and transform objects. Therefore, it needs a lot of PHP reflection and other tricks to make it work.</p><p>The type system is one of those tricks; it keeps track of the types within your data objects. For laravel-data v4, this system was entirely built up from the ground again (twice actually; the first implementation was trashed after a few months) to be more versatile and performant.</p><p>The most notable change you&apos;ll probably see of this rewrite is better performance and support for PHP 8.2 BNF types:</p><pre><code class="language-php">class BnfData extends Data
{
    public function __construct(
        public (InterfaceA&amp;InterfaceB)|null $aBNFType
    )
}
</code></pre><h2 id="small-changes">Small changes</h2><ul><li>We now require Laravel 10. As soon as Laravel 11 comes out, we&apos;ll, of course, support it</li><li>Laravel Model attributes can now also be used to create data</li><li>The <code>from</code> method now can be called without arguments</li><li>The docs have been rewritten to be more coherent</li><li>We&apos;ve restructured the tests around the different features of the package to make testing easier</li></ul><p>For this release we&apos;ve also created an extensive upgrade guide, you&apos;ll find it <a href="https://github.com/spatie/laravel-data/blob/main/UPGRADING.md?ref=rubenvanassche.com">here</a>.</p><h2 id="whats-next">What&apos;s next</h2><p>I&apos;ve been working hours on this release for the last weeks, neglecting new PRs and issues. Next week, I&apos;m going to skim through them all. Plus, I expect a lot of new issues and PRs for this new version of the package.</p><p>After that, it is time to work further on typescript-transformer v3, which is completely rewritten but needs some more love.</p><p>After typescript-transformer, I&apos;m going to start looking at laravel-data 5. This will be a validation-heavy release where we try to iron out the kinks in the validation process as we did in this release with the partials system.</p>]]></content:encoded></item><item><title><![CDATA[Sending a request with HMAC SHA256 signature using Postman]]></title><description><![CDATA[Make debugging easier by generating signatures right within Postmark.]]></description><link>https://rubenvanassche.com/sending-a-request-with-hmac-sha256-signatures-using-postman/</link><guid isPermaLink="false">6492c1a339f3f704de50872a</guid><category><![CDATA[PHP]]></category><category><![CDATA[Postman]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Wed, 21 Jun 2023 09:29:51 GMT</pubDate><content:encoded><![CDATA[<p>Today I was working on <a href="https://flareapp.io/?ref=rubenvanassche.com">Flare</a>, fixing a bug where a webhook payload didn&apos;t work. Webhooks allow applications to communicate with each other without constant polling for updates. When a change happens, a request is sent from one service to another, indicating what happens.</p><p>The web is an open place with many bad actors, so a signature is sent with this request as a protection measure. This signature is constructed from a secret key that both sender and receiver know and the contents of the payload.</p><p>In PHP, the sender would create a request and then a signature as follows:</p><pre><code class="language-php">$signature = hash_hmac(
 &apos;sha256&apos;,
 $payload,
 &apos;some_secret_key&apos;
);
</code></pre><p>This signature is then appended as a header to the request.</p><p>The receiver on the other side would then again construct the signature like above and then check if it corresponds with the signature sent:</p><pre><code class="language-php">hash_equals($request-&gt;header(&apos;signature&apos;), $signature);
</code></pre><p>If the two correspond, we know no one tampered with the message, and the request was sent by the service we trust (not some other bad actor who found out what the endpoint was).</p><h2 id="local-signature-mess">Local signature mess</h2><p>That&apos;s all great. Until you start debugging errors why something is not working, Postman is one of the best tools to send API requests from your computer when you&apos;re debugging.</p><p>But since the signature depends on the contents of the request payload, it needs to be recalculated each time the payload changes.</p><p>Luckily Postman has a solution for this! It is possible to run some JavaScript code before the request is sent, which allows you to change the request right before it is sent out. You can write this code within the &quot;Pre-request Script&quot; tab:</p><figure class="kg-card kg-image-card"><img src="https://rubenvanassche.com/content/images/2023/06/screenshot-9.png" class="kg-image" alt loading="lazy" width="2000" height="133" srcset="https://rubenvanassche.com/content/images/size/w600/2023/06/screenshot-9.png 600w, https://rubenvanassche.com/content/images/size/w1000/2023/06/screenshot-9.png 1000w, https://rubenvanassche.com/content/images/size/w1600/2023/06/screenshot-9.png 1600w, https://rubenvanassche.com/content/images/size/w2400/2023/06/screenshot-9.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>To add a signature header, we use this piece of code:</p><pre><code class="language-javascript">const secret =&apos;some_secret_key&apos;;

var signature = CryptoJS.HmacSHA256(request.data, secret).toString(CryptoJS.digest);

pm.request.addHeader({
 key: &apos;signature&apos;,
 value: signature
});

console.log(&quot;Signature :: &quot; + signature)
</code></pre><p>This code will add a signature header with an HMAC SHA256 hashed version of the secret and payload content, neat!</p>]]></content:encoded></item><item><title><![CDATA[Sensitive parameters in PHP 8.2]]></title><description><![CDATA[Debugging errors can leak sensitive information, let's see how we can solve this using PHP 8.2]]></description><link>https://rubenvanassche.com/sensitive-parameters-in-php-8-2/</link><guid isPermaLink="false">6492c53539f3f704de50875c</guid><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Mon, 19 Jun 2023 09:40:00 GMT</pubDate><content:encoded><![CDATA[<p><strong>This post was first published on the Flare blog.</strong></p><p>When debugging errors, we as developers have a few tools available: an exception of a particular class, a message, a status code, and a stack trace. The stack trace is a report that provides information about the sequence of function calls that led to an error or an exception. It shows the order in which functions were called.</p><p>We name each call to a function/method within a stack trace a frame. It has lots of useful info like the file name, line number, function name, class (sometimes), and arguments that were used to make the call. Having arguments within a stack trace can be dangerous. For example, let&apos;s say you have a function like this:</p><pre><code class="language-php">function getPayments(string $apiKey): array 
{
    throw new Exception(&apos;Could not fetch payments&apos;);
}
</code></pre><p>Usually, you would have some application code here, fetching payments through an external API, but to simplify things, let&apos;s go straight to the case where we throw an exception because the payments couldn&apos;t be fetched.</p><p>You can call this function as such:</p><pre><code class="language-php">getPayments(&apos;dj8gd4sl05db32xjch7989dsxws&apos;);
</code></pre><p>This function call, of course, fails miserably, providing you with the following exception message:</p><pre><code>Fatal error: Uncaught Exception: Could not fetch payments in /in/UoFa1:6
Stack trace:
#0 /in/UoFa1(32): getPayments(&apos;dj8gd4sl05db32x...&apos;)
#1 {main}
  thrown in /in/UoFa1 on line 6
&lt;br/&gt;&lt;i&gt;Process exited with code &lt;b title=&quot;Generic Error&quot;&gt;255&lt;/b&gt;.&lt;/i&gt;
</code></pre><p>Take a look at the third line:</p><pre><code>#0 /index.php(34): getPayments(&apos;dj8gd4sl05db32x...&apos;)
</code></pre><p>That&apos;s our API key in plain sight, which we want to avoid at all costs!</p><p>Above, we saw the stringified version of the stack trace. When we catch the exception, we can take a look at a more normalized array version:</p><pre><code class="language-php">try {
    getPayments(&apos;dj8gd4sl05db32xjch7989dsxws&apos;);
} catch (Exception $e) {
    var_dump($e-&gt;getTrace());
}
</code></pre><p>Catching the exception gives us the following stack trace:</p><pre><code class="language-php">array(1) {
  [0]=&gt;
  array(4) {
    [&quot;file&quot;]=&gt;
    string(9) &quot;/index.php&quot;
    [&quot;line&quot;]=&gt;
    int(34)
    [&quot;function&quot;]=&gt;
    string(11) &quot;getPayments&quot;
    [&quot;args&quot;]=&gt;
    array(1) {
      [0]=&gt;
      string(27) &quot;dj8gd4sl05db32xjch7989dsxws&quot;
    }
  }
}
</code></pre><p>As you can see, we&apos;re passing one argument to the <code>getPayments</code> function, which can be helpful when debugging. But this information can also end up in the wrong hands when displayed in logs or error pages like Ignition.</p><p>That&apos;s the reason why we refrained from showing these parameters in Ignition:</p><figure class="kg-card kg-image-card"><img src="https://rubenvanassche.com/content/images/2023/06/eudbeHy9J2NH593nb6QDV4T888wBsEKJWU24LjIu.png" class="kg-image" alt loading="lazy" width="2000" height="1559" srcset="https://rubenvanassche.com/content/images/size/w600/2023/06/eudbeHy9J2NH593nb6QDV4T888wBsEKJWU24LjIu.png 600w, https://rubenvanassche.com/content/images/size/w1000/2023/06/eudbeHy9J2NH593nb6QDV4T888wBsEKJWU24LjIu.png 1000w, https://rubenvanassche.com/content/images/size/w1600/2023/06/eudbeHy9J2NH593nb6QDV4T888wBsEKJWU24LjIu.png 1600w, https://rubenvanassche.com/content/images/size/w2400/2023/06/eudbeHy9J2NH593nb6QDV4T888wBsEKJWU24LjIu.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>And thus, due to the tight coupling of Flare and Ignition, these parameters won&apos;t end up in Flare.</p><p>But this all might change due to a recent addition to PHP 8.2.</p><h2 id="sensitive-parameters">Sensitive Parameters</h2><p>Starting from PHP 8.2, some parameters can now be tagged as &apos;sensitive&apos; using an attribute, meaning a parameter will be hidden from a stack trace.</p><p>Let&apos;s rewrite our <code>getPayments</code> function as follows:</p><pre><code class="language-php">function getPayments(
    #[SensitiveParameter]
    string $apiKey
): array {
    throw new Exception(&apos;Could not fetch payments&apos;);
}
</code></pre><p>Now when we execute the function, we get the following:</p><pre><code>Fatal error: Uncaught Exception: Could not fetch payments in /in/Ko3d8:13
Stack trace:
#0 /in/Ko3d8(32): getPayments(Object(SensitiveParameterValue))
#1 {main}
  thrown in /in/Ko3d8 on line 13
&lt;br/&gt;&lt;i&gt;Process exited with code &lt;b title=&quot;Generic Error&quot;&gt;255&lt;/b&gt;.&lt;/i&gt;
</code></pre><p>The API key is replaced with <code>Object(SensitiveParameterValue)</code>, hiding the original value! Let&apos;s see how the stack trace looks:</p><pre><code class="language-php">array(1) {
  [0]=&gt;
  array(4) {
    [&quot;file&quot;]=&gt;
    string(9) &quot;/in/cv0er&quot;
    [&quot;line&quot;]=&gt;
    int(33)
    [&quot;function&quot;]=&gt;
    string(11) &quot;getPayments&quot;
    [&quot;args&quot;]=&gt;
    array(1) {
      [0]=&gt;
      object(SensitiveParameterValue)#2 (0) {
      }
    }
  }
}
</code></pre><p>Again the API key is gone from traces, but the value still exists. The original string value is replaced with an object <code>SensitiveParameterValue</code>, which wraps the original value. We can see this by retrieving the original value as such:</p><pre><code class="language-php">try {
    getPayments(&apos;dj8gd4sl05db32xjch7989dsxws&apos;);
} catch (Exception $e) {
    var_dump($e-&gt;getTrace()[0][&apos;args&apos;][0]-&gt;getValue()); // dj8gd4sl05db32xj...
}

</code></pre><h2 id="classes">Classes</h2><p>The example mentioned above, used functions to make things easy to understand, but you can perfectly tag parameters as sensitive in a class:</p><pre><code class="language-php">class PaymentApi
{
    public function getPayments(
    	 int $tenantId,
        #[SensitiveParameter]
        string $apiKey
    ): string {
        throw new Exception(&apos;Could not fetch payments&apos;);
    }
}
</code></pre><p>Running this:</p><pre><code class="language-php">try {
    $api = new PaymentApi();
    $api-&gt;getPayments(314, &apos;dj8gd4sl05db32xjch7989dsxws&apos;);
} catch (Exception $e) {
    var_dump($e-&gt;getTrace());
}
</code></pre><p>It gives us the following stack trace:</p><pre><code class="language-php">array(1) {
  [0]=&gt;
  array(6) {
    [&quot;file&quot;]=&gt;
    string(90) &quot;/index.php&quot;
    [&quot;line&quot;]=&gt;
    int(36)
    [&quot;function&quot;]=&gt;
    string(11) &quot;getPayments&quot;
    [&quot;class&quot;]=&gt;
    string(10) &quot;PaymentApi&quot;
    [&quot;type&quot;]=&gt;
    string(2) &quot;-&gt;&quot;
    [&quot;args&quot;]=&gt;
    array(2) {
      [0]=&gt;
      int(314)
      [1]=&gt;
      object(SensitiveParameterValue)#3 (0) {
      }
    }
  }
}
</code></pre><p>As you can see, the <code>tenantId</code> is a non-sensitive parameter, so its value still appears in the stack trace. However, <code>apiKey</code>, the sensitive parameter value, has been wrapped into a <code>SensitiveParameterValue</code> object.</p><h2 id="flare">Flare</h2><p>As mentioned, we don&apos;t support stack trace arguments in Ignition for security reasons, and this new sensitive parameter feature might let us rethink this. On our roadmap, I&apos;ve created a new <a href="https://github.com/spatie/flareapp.io-roadmap/discussions/71?ref=rubenvanassche.com">feature request</a>, allowing arguments to be shown in Ignition and Flare stack traces. Give it a thumbs up if you want to see this implemented.</p>]]></content:encoded></item><item><title><![CDATA[Changing your larger-than-average MySQL table]]></title><description><![CDATA[We're still working on our redesign and are close to release. Today we started tackling an issue where the performance of the error page was too slow for us. In the end, we needed to change the structure of the error occurrences table, which is a lot harder than it seems.]]></description><link>https://rubenvanassche.com/changing-your-larger-than-average-mysql-table/</link><guid isPermaLink="false">6492c49039f3f704de508749</guid><category><![CDATA[PHP]]></category><category><![CDATA[MySQL]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Mon, 29 May 2023 09:37:00 GMT</pubDate><content:encoded><![CDATA[<p><strong>This post was first published on the Flare blog.</strong></p><p>We&apos;re still working on our redesign and are close to release. Today we started tackling an issue where the performance of the error page was too slow for us. In the end, we needed to change the structure of the error occurrences table, which is a lot harder than it seems.</p><p>In the redesign of Flare, we&apos;ve added a new feature called insights which gives you a quick overview of some statistics like which endpoints triggered the error, which users, which application versions, and so on.</p><figure class="kg-card kg-image-card"><img src="https://rubenvanassche.com/content/images/2023/06/MYV0QK4DiTMzBKdkUYDeSDEjb0fttcieodbR0zAY.png" class="kg-image" alt loading="lazy" width="2000" height="392" srcset="https://rubenvanassche.com/content/images/size/w600/2023/06/MYV0QK4DiTMzBKdkUYDeSDEjb0fttcieodbR0zAY.png 600w, https://rubenvanassche.com/content/images/size/w1000/2023/06/MYV0QK4DiTMzBKdkUYDeSDEjb0fttcieodbR0zAY.png 1000w, https://rubenvanassche.com/content/images/size/w1600/2023/06/MYV0QK4DiTMzBKdkUYDeSDEjb0fttcieodbR0zAY.png 1600w, https://rubenvanassche.com/content/images/size/w2400/2023/06/MYV0QK4DiTMzBKdkUYDeSDEjb0fttcieodbR0zAY.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>The counts for these insights are always live calculated, which means queries like this:</p><pre><code class="language-sql">SELECT
	count(DISTINCT entry_point) AS aggregate
FROM
	`error_occurrences`
WHERE
	`error_occurrences`.`error_id` = ?
	AND `error_occurrences`.`error_id` IS NOT NULL
	AND `entry_point_type` = &apos;web&apos;;
</code></pre><p>Each error occurrence has an <code>entry_point_type</code> (web, queue, command) and an <code>entry_point</code> (a URL, job class, or command). These queries were quite performant on our seeded data, but in production, we have an error that has accumulated 66k occurrences. That&apos;s the point where things start to become slow.</p><p>Luckily, there&apos;s an easy solution to this, indexes! So we&apos;ve added an index to <code>entry_point_type</code> and saw a performance boost. Later on, we tried adding an index to <code>entry_point</code>, but this exception popped up:</p><pre><code>BLOB/TEXT column &apos;entry_point&apos; used in key specification without a key length
</code></pre><p>Wait! Are we using a text type column for <code>entry_point</code>? That&apos;s a bit crazy. These columns are stored differently than varchars in MySQL, which makes them a lot slower + indexing them has some limitations. Let&apos;s fix this using a varchar column, but how many characters should it be?</p><p>The first thing we checked was how many characters are required on average. A quick query that counted the number of occurrences for each <code>entry_point</code> length gave us the following:</p><figure class="kg-card kg-image-card"><img src="https://rubenvanassche.com/content/images/2023/06/SkgnhKxFojEB1WTCZQIkskbWqTDhCxsKD7kGNfwr.png" class="kg-image" alt loading="lazy" width="2000" height="836" srcset="https://rubenvanassche.com/content/images/size/w600/2023/06/SkgnhKxFojEB1WTCZQIkskbWqTDhCxsKD7kGNfwr.png 600w, https://rubenvanassche.com/content/images/size/w1000/2023/06/SkgnhKxFojEB1WTCZQIkskbWqTDhCxsKD7kGNfwr.png 1000w, https://rubenvanassche.com/content/images/size/w1600/2023/06/SkgnhKxFojEB1WTCZQIkskbWqTDhCxsKD7kGNfwr.png 1600w, https://rubenvanassche.com/content/images/size/w2400/2023/06/SkgnhKxFojEB1WTCZQIkskbWqTDhCxsKD7kGNfwr.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>We&apos;re pretty safe with 255 characters because almost all entry points for occurrences are around that length. Because a URL is somewhat limited to 2048 characters and varchars smartly assign space (it only takes the space required for a value), we took 2048 characters for this column.</p><p>Now changing our column can be done as such:</p><pre><code class="language-php">ALTER TABLE `flare`.`error_occurrences`
CHANGE `entry_point` `entry_point` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL;
</code></pre><p>The thing is, this is a large table at the moment. There are 10531985 it.. 10532202 ite.. 10532278 items. You get the point. A lot of items, and the table is constantly growing.</p><p>By default, MySQL will make a copy of this table, apply the changes to that table, remove the old table, and use the newly created one instead. This process is extremely slow, and it blocks a lot of statements, completely shutting down Flare. We should avoid this at all costs.</p><h2 id="the-trick-with-the-extra-column">The trick with the extra column</h2><p>There are a few solutions to this problem. In our case, we did the following: we first created a new column called <code>entry_point_2</code> with the correct type:</p><pre><code class="language-sql">ALTER TABLE `flare`.`error_occurrences` 
ADD COLUMN `entry_point_2` varchar(2048) NULL, ALGORITHM=INSTANT;
</code></pre><p>The MySQL instant algorithm will instantly add the column (it still takes some time, but no locks are required). The problem? When we ran this query, MySQL started copying our table, which was the thing we were trying to avoid.</p><p>After some research work in the MySQL documentation, we discovered that these operations will still use the copy algorithm if a fulltext index exists on the table, even when that index isn&apos;t used in the operation.</p><p>So the easy fix was to drop the fulltext index temporarily:</p><pre><code class="language-sql">ALTER TABLE `flare`.`error_occurrences` 
DROP INDEX `error_message_fulltext`;
</code></pre><p>Great, we can now add an extra column without too much hassle.</p><h2 id="moving-data">Moving data</h2><p>Next, we move the data from <code>entry_point</code> to <code>entry_point_2</code>. This operation is going to take some time. The cool thing is we can already ensure that newly created occurrences immediately fill <code>entry_point_2</code>.</p><p>Doing such a thing would require some changes to the code, then deploying that code, later changing the code, deploying that code again ... too much complexity if you ask me.</p><p>MySQL has a feature called triggers, which are small hooks running before or after inserting, updating, or deleting a row. Since we only add error occurrences, an insert trigger was enough to make sure each new occurrence has its <code>entry_point_2</code> set:</p><pre><code class="language-sql">CREATE TRIGGER `flare`.`error_occurrences`
	BEFORE INSERT ON `error_occurrences`
	FOR EACH ROW
	BEGIN
	SET NEW.`entry_point_2` = NEW.`entry_point`;
END
</code></pre><p>Now we can update all the other entries within the table which are missing a value for <code>entry_point_2</code>:</p><pre><code class="language-sql">UPDATE
	error_occurrences
SET
	entry_point_2 = entry_point
WHERE
	entry_point IS NOT NULL
	AND entry_point_2 IS NULL
</code></pre><p>There are probably more performant ways to this (it took a whopping 20 minutes to execute this query), but it doesn&apos;t add locks to our table and is easy to write.</p><h2 id="setting-everything-right">Setting everything right</h2><p>So we&apos;re almost there. We&apos;ve got identical columns, <code>entry_point</code> of type text and <code>entry_point_2</code> of type varchar(2048). We&apos;re going to rename these columns so that:</p><ul><li><code>entry_point</code> -&gt; <code>old_entry_point</code></li><li><code>entry_point_2</code> -&gt; <code>entry_point</code></li></ul><p>We can do this instantly like this:</p><pre><code class="language-sql">ALTER TABLE  `flare`.`error_occurrences` 
RENAME COLUMN entry_point TO old_entry_point, 
RENAME COLUMN entry_point_2 TO entry_point;
</code></pre><p>We remove the trigger we&apos;ve created:</p><pre><code class="language-sql">DROP TRIGGER `flare`.`error_occurrences`;
</code></pre><p>And in the end, altogether remove the <code>old_entry_point</code> column:</p><pre><code class="language-sql">ALTER TABLE `flare`.`error_occurrences` 
DROP COLUMN `old_entry_point`, ALGORITHM=INSTANT;
</code></pre><p>Now we can create an index on the newly created <code>entry_point</code> column:</p><pre><code class="language-sql">ALTER TABLE `flare`.`error_occurrences`
ADD INDEX `error_occurrences_entry_point_index` (`entry_point`(255)) USING BTREE;
</code></pre><p>And, of course, we also need to create the fulltext index we removed earlier. The thing is, we can only add such an index when nothing is written to the table. To fix this, we temporarily paused Laravel Horizon:</p><pre><code class="language-bash">php artisan horizon:pause
</code></pre><p>Our queues are now paused so that nothing gets written to this table. We don&apos;t lose anything thanks to Horizon because the jobs we&apos;ve added still exist, and they can be executed as soon as we restart Horizon again.</p><p>Now we can add the fulltext index as such:</p><pre><code class="language-sql">ALTER TABLE `flare`.`error_occurrences`
ADD FULLTEXT INDEX `error_message_fulltext` (`exception_message`);
</code></pre><p>And restart horizon:</p><pre><code class="language-bash">php artisan horizon:continue
</code></pre><h2 id="conclusion">Conclusion</h2><p>A minor fix turned out to be more complicated than expected, but the result is what counts. And our page now got a lot faster. We&apos;ll start inviting beta testers soon. Interested? Contact us at <a>support@flareapp.io</a>.</p>]]></content:encoded></item><item><title><![CDATA[Optimizing Flare]]></title><description><![CDATA[We're redesigning Flare, but our application became really slow. Let's find out why and fix it!]]></description><link>https://rubenvanassche.com/optimizing-flare/</link><guid isPermaLink="false">6437f2f6a33d4d053d693dc2</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Mon, 03 Apr 2023 12:18:00 GMT</pubDate><content:encoded><![CDATA[<p><strong>This post first appeared on the Flare blog.</strong></p><p>Our whole team is currently working hard on the next version of Flare. We&apos;re completely redesigning the application and website, and it is an immense effort, but it will undoubtedly pay off in the future.</p><p>In this rewrite of Flare, we decided to move from Laravel resources to our own Laravel data package for sending data to the front end. Doing this not only adds the benefit of a fully typed response, but we also get TypeScript definitions for our resources without extra effort.</p><p>Once all resources were manually converted into data objects, we were excited to check if everything worked fine. Though we didn&apos;t hit on any exceptions or errors, some application pages felt extremely slow, too slow to be useful for our clients.</p><p>We quickly found that much of the response time was spent on the data objects. We started by optimizing the data objects using the lazy functionality, which allows loading certain parts of the data later on when the initial page is already sent. It made the pages faster, but we didn&apos;t want to stop there since we&apos;re completely redesigning the whole application.</p><p>The laravel-data package is fantastic to work with, but it also adds a lot of complexity when outputting data. In this blog post, we will look at how we&apos;ve improved the performance of the package and, thus, the complete Flare application.</p><h2 id="new-tools-to-play-with">New tools to play with</h2><p>To get started, I will introduce you to three tools you can use within your toolset to research performance problems: Xdebug, PHPBench, and QCacheGrind.</p><p>Xdebug is a PHP extension that provides debugging and profiling capabilities. It is most famous for its complicated configuration, which may slow down your whole application. Luckily, Xdebug version 3 made extensive progress on this part, and setting up Xdebug is now relatively easy.</p><p>PHPBench is a benchmarking tool for PHP that can help you measure the performance of your code. It allows you to create test cases and run them multiple times to get a notion of the speed of your code.</p><p>Lastly, QCacheGrind is an application allowing you to look at the profiles generated by Xdebug visually. QCacheGrind is available for MacOS and Windows. If you&apos;re on Linux, look at KCachegrind, which is almost the same.</p><h2 id="getting-a-baseline">Getting a baseline</h2><p>To start, we need to be able to test if the changes we make to our code have a positive or negative impact on the performance. The best way to do this is by creating a few benchmarks using PHPBench. These benchmarks will be run multiple times, and the average time this take can be used as a metric to see if we&apos;ve made a difference by updating the code.</p><p>PHPBench works a lot like PHPUnit. You create a few benchmark files (like tests) with benchmark cases (like test cases). The PHPBench command will then execute these files like the PHPUnit command would. Only it executes the cases multiple times and tracks how long it takes.</p><p>We get started as usual by installing PHPBench:</p><pre><code class="language-bash">composer require phpbench/phpbench --dev
</code></pre><p>Next, we create a <code>phpbench.json</code> file which configures PHPbench:</p><pre><code class="language-json">{
     &quot;$schema&quot;:&quot;./vendor/phpbench/phpbench/phpbench.schema.json&quot;,
     &quot;runner.bootstrap&quot;: &quot;vendor/autoload.php&quot;,
     &quot;runner.path&quot; : &quot;benchmarks&quot;
 }
</code></pre><p>The <code>runner.path</code> option defines the directory where our benchmarks are located, so we create a new directory for them:</p><pre><code class="language-bash">mkdir benchmarks
</code></pre><p>The last thing to do is create the benchmark. We call this file <code>ExampleBench.php</code>. Notice the <code>Bench</code> suffix. Using this suffix is essential because, otherwise, PHPBench won&apos;t execute the file. Within the <code>ExampleBench.php</code>, we add an <code>ExampleBench </code> class:</p><pre><code class="language-php">class ExampleBench
{
     
}
</code></pre><p>Let&apos;s create a first benchmark:</p><pre><code class="language-php">use PhpBench\Attributes\Iterations;
use PhpBench\Attributes\Revs;

class ExampleBench
{
    #[Revs(500), Iterations(5)]
    public function benchPow()
    {
		pow(2, 10);
    }
}
</code></pre><p>As you can see, we will benchmark how long it takes to calculate 2^10. The benchmark case we&apos;ve added is called <code>benchPow</code>. Notice the <code>bench</code> keyword prefixed, which is required for PHPBench to find the benchmark case.</p><p>We&apos;ve also added two attributes to the method:</p><p>Revs, short for revolutions, refers to the number of times the benchmark is executed consecutively within a single time measurement. The more revolutions, the more accurate the result. In this case, we will calculate 2^10 a whopping 500 times!</p><p>In an ideal world, we&apos;re done and can start measuring. But running these revolutions multiple times is safer and more correct to ensure our measurements are stable and don&apos;t differ too much.</p><p>We have five iterations of 500 revolutions so that the pow function will be executed for 5 * 500 = 2500 times.</p><p>Now it is time to run PHPBench:</p><pre><code class="language-bash">vendor/bin/phpbench run  --report=default
</code></pre><p>And we get the following result:</p><pre><code>PHPBench (1.2.9) running benchmarks... #standwithukraine
with configuration file: /Users/ruben/Spatie/laravel-data/phpbench.json
with PHP version 8.2.3, xdebug &#x2714;, opcache &#x274C;

\ExampleBench

    benchPow................................I4 - Mo0.038&#x3BC;s (&#xB1;3.33%)

Subjects: 1, Assertions: 0, Failures: 0, Errors: 0
+------+--------------+----------+-----+------+------------+----------+--------------+----------------+
| iter | benchmark    | subject  | set | revs | mem_peak   | time_avg | comp_z_value | comp_deviation |
+------+--------------+----------+-----+------+------------+----------+--------------+----------------+
| 0    | ExampleBench | benchPow |     | 500  | 1,812,376b | 0.036&#x3BC;s  | -1.58&#x3C3;       | -5.26%         |
| 1    | ExampleBench | benchPow |     | 500  | 1,812,376b | 0.038&#x3BC;s  | +0.00&#x3C3;       | +0.00%         |
| 2    | ExampleBench | benchPow |     | 500  | 1,812,376b | 0.038&#x3BC;s  | +0.00&#x3C3;       | +0.00%         |
| 3    | ExampleBench | benchPow |     | 500  | 1,812,376b | 0.038&#x3BC;s  | +0.00&#x3C3;       | +0.00%         |
| 4    | ExampleBench | benchPow |     | 500  | 1,812,376b | 0.040&#x3BC;s  | +1.58&#x3C3;       | +5.26%         |
+------+--------------+----------+-----+------+------------+----------+--------------+----------------+
</code></pre><p>It looks like 0.038&#x3BC;s is the best average we have. Let&apos;s see what&apos;s the fastest:</p><ol><li>2^20</li><li>2^10 * 2^10</li></ol><p>Our benchmark now will look like this:</p><pre><code class="language-php">class ExampleBench
{
    #[Revs(500), Iterations(5)]
    public function benchPow()
    {
        pow(2, 20);
    }

    #[Revs(500), Iterations(5)]
    public function benchPowPow()
    {
        pow(2, 10) * pow(2, 10);
    }
}
</code></pre><p>Running PHPBench gives the following results:</p><pre><code>PHPBench (1.2.9) running benchmarks... #standwithukraine
with configuration file: /Users/ruben/Spatie/laravel-data/phpbench.json
with PHP version 8.2.3, xdebug &#x2714;, opcache &#x274C;

\ExampleBench

    benchPow................................I4 - Mo0.039&#x3BC;s (&#xB1;3.78%)
    benchPowPow.............................I4 - Mo0.060&#x3BC;s (&#xB1;11.25%)

Subjects: 2, Assertions: 0, Failures: 0, Errors: 0
+------+--------------+-------------+-----+------+------------+----------+--------------+----------------+
| iter | benchmark    | subject     | set | revs | mem_peak   | time_avg | comp_z_value | comp_deviation |
+------+--------------+-------------+-----+------+------------+----------+--------------+----------------+
| 0    | ExampleBench | benchPow    |     | 500  | 1,812,376b | 0.040&#x3BC;s  | +0.27&#x3C3;       | +1.01%         |
| 1    | ExampleBench | benchPow    |     | 500  | 1,812,376b | 0.042&#x3BC;s  | +1.60&#x3C3;       | +6.06%         |
| 2    | ExampleBench | benchPow    |     | 500  | 1,812,376b | 0.038&#x3BC;s  | -1.07&#x3C3;       | -4.04%         |
| 3    | ExampleBench | benchPow    |     | 500  | 1,812,376b | 0.038&#x3BC;s  | -1.07&#x3C3;       | -4.04%         |
| 4    | ExampleBench | benchPow    |     | 500  | 1,812,376b | 0.040&#x3BC;s  | +0.27&#x3C3;       | +1.01%         |
| 0    | ExampleBench | benchPowPow |     | 500  | 1,812,376b | 0.076&#x3BC;s  | +1.47&#x3C3;       | +16.56%        |
| 1    | ExampleBench | benchPowPow |     | 500  | 1,812,376b | 0.072&#x3BC;s  | +0.93&#x3C3;       | +10.43%        |
| 2    | ExampleBench | benchPowPow |     | 500  | 1,812,376b | 0.060&#x3BC;s  | -0.71&#x3C3;       | -7.98%         |
| 3    | ExampleBench | benchPowPow |     | 500  | 1,812,376b | 0.060&#x3BC;s  | -0.71&#x3C3;       | -7.98%         |
| 4    | ExampleBench | benchPowPow |     | 500  | 1,812,376b | 0.058&#x3BC;s  | -0.98&#x3C3;       | -11.04%        |
+------+--------------+-------------+-----+------+------------+----------+--------------+----------------+
</code></pre><p>So basically, it costs 50% more time to calculate two <code>pow(2, 10)</code> functions than one <code>pow(2, 20)</code>, that&apos;s quite useful!</p><p>Now back to laravel-data, we want to know how fast we can create a data object and how fast we can transform it into a JSON object. We can describe this as follows:</p><pre><code class="language-php">class DataBench
{
	#[Revs(500), Iterations(2)]
    public function benchDataCreation()
    {
        MultiNestedData::from([
            &apos;nested&apos; =&gt; [&apos;simple&apos; =&gt; &apos;Hello&apos;],
            &apos;nestedCollection&apos; =&gt; [
                [&apos;simple&apos; =&gt; &apos;Flare&apos;],
                [&apos;simple&apos; =&gt; &apos;is&apos;],
                [&apos;simple&apos; =&gt; &apos;awesome&apos;],
            ],
        ]);
    }

    #[Revs(500), Iterations(2)]
    public function benchDataTransformation()
    {
        $data = new MultiNestedData(
            new NestedData(new SimpleData(&apos;Hello&apos;)),
            new DataCollection(NestedData::class, [
                new NestedData(new SimpleData(&apos;Flare&apos;)),
                new NestedData(new SimpleData(&apos;is&apos;)),
                new NestedData(new SimpleData(&apos;awesome&apos;)),
            ])
        );

        $data-&gt;toArray();
    }

    #[Revs(500), Iterations(2)]
    public function benchDataCollectionCreation()
    {
        $collection = Collection::times(
            15,
            fn() =&gt; [
                &apos;nested&apos; =&gt; [&apos;simple&apos; =&gt; &apos;Hello&apos;],
                &apos;nestedCollection&apos; =&gt; [
                    [&apos;simple&apos; =&gt; &apos;Flare&apos;],
                    [&apos;simple&apos; =&gt; &apos;is&apos;],
                    [&apos;simple&apos; =&gt; &apos;awesome&apos;],
                ],
            ]
        )-&gt;all();

        MultiNestedData::collection($collection);
    }

    #[Revs(500), Iterations(2)]
    public function benchDataCollectionTransformation()
    {
        $collection = Collection::times(
            15,
            fn() =&gt; new MultiNestedData(
                new NestedData(new SimpleData(&apos;Hello&apos;)),
                new DataCollection(NestedData::class, [
                    new NestedData(new SimpleData(&apos;Flare&apos;)),
                    new NestedData(new SimpleData(&apos;is&apos;)),
                    new NestedData(new SimpleData(&apos;awesome&apos;)),
                ])
            )
        )-&gt;all();

        $collection = MultiNestedData::collection($collection);

        $collection-&gt;toArray();
    }
}
</code></pre><p>In the first benchmark, we&apos;re creating a data object using arrays. In the second one, we created an object as efficiently as possible by manually defining it and then transforming it to JSON.</p><p>We also added two cases where we do the same, but instead of using data objects, we benchmark using data collections of multiple data objects.</p><p>Now running PHPBench will fail because the laravel-data package depends on some Laravel functionality. Luckily Laravel provides excellent testing infrastructure, which we can use within our benchmark. We do this by using the <code>CreatesApplication</code> trait, which is also present in the base Laravel test case:</p><pre><code class="language-php">use Tests\CreatesApplication;

class DataBench
{
    use CreatesApplication;

    public function __construct()
    {
        $this-&gt;createApplication();
    }
    
    // The benchmarks
}   
</code></pre><p>We need another update because we&apos;re benchmarking a laravel package, not a laravel application. This means we must use the <code>CreatesApplication</code> trait from the <code>orchestra/testbench</code> package, which is used to test Laravel packages. And we also need to specify the laravel-data service provider to boot up the package:</p><pre><code class="language-php">use Orchestra\Testbench\Concerns\CreatesApplication;

class DataBench
{
    use CreatesApplication;

    public function __construct()
    {
        $this-&gt;createApplication();
    }

    protected function getPackageProviders($app)
    {
        return [
            LaravelDataServiceProvider::class,
        ];
    }
    
    // The benchmarks
} 
</code></pre><p>Great, we now have some benchmarks ready to measure the speed of our code!</p><h2 id="getting-started-with-xdebug">Getting started with Xdebug</h2><p>Next up, we need to profile our code. When we&apos;re profiling our code, we will run it a bit differently than like would typically. When profiling the PHP process will write down each function call we make and also keep track of how long it takes to run the function. Ultimately, all this information will be written into a file we can analyze.</p><p>To get this working, we need to install and enable Xdebug. You can find the instructions <a href="https://xdebug.org/docs/install?ref=rubenvanassche.com">here</a>.</p><p>For me, on a Mac, it was as simple as this:</p><pre><code class="language-bash">sudo pecl install xdebug
</code></pre><p>We also need to update or create the Xdebug .ini file:</p><pre><code class="language-ini">[xdebug]

xdebug.mode=profile
xdebug.start_with_request=yes
</code></pre><p>On my Mac, I&apos;ve put this file here: <code>nano /opt/homebrew/etc/php/8.2/conf.d/xdebug.ini</code>.</p><p>We configure two options:</p><ul><li><code>xdebug.mode=profile</code> to enable profiling in Xdebug.</li><li><code>xdebug.start_with_request=yes</code> to start profiling with PHPBench. When you&apos;re done with profiling, don&apos;t forget to set this to <code>off</code> or <code>trigger</code>. Otherwise, all your PHP applications will take ages to load.</li></ul><p>We have our benchmarks, and we have our profiler. Now we only need to combine those two, and then we can discover why our code is so slow!</p><h2 id="profiling-the-code">Profiling the code</h2><p>We will rerun our benchmarks, but this time we will also profile the benchmarks. We can do this with the following command:</p><pre><code class="language-bash">vendor/bin/phpbench xdebug:profile
</code></pre><p>Which gives the following output:</p><pre><code>PHPBench (1.2.9) running benchmarks... #standwithukraine
with configuration file: /Users/ruben/Spatie/laravel-data/phpbench.json
with PHP version 8.2.3, xdebug &#x2714;, opcache &#x274C;

\DataBench

    benchDataCreation.......................I0 - Mo10.762ms (&#xB1;0.00%)
    benchDataTransformation.................I0 - Mo1.095ms (&#xB1;0.00%)
    benchDataCollectionCreation.............I0 - Mo159.879ms (&#xB1;0.00%)
    benchDataCollectionTransformation.......I0 - Mo13.952ms (&#xB1;0.00%)

Subjects: 4, Assertions: 0, Failures: 0, Errors: 0

4 profile(s) generated:

/Users/ruben/Spatie/laravel-data/.phpbench/xdebug-profile/3fcb020036c8d7e8efdcddd4dbd66b92.cachegrind.gz
/Users/ruben/Spatie/laravel-data/.phpbench/xdebug-profile/8deba1dcce573e1bf818772ac7a5ace0.cachegrind.gz
/Users/ruben/Spatie/laravel-data/.phpbench/xdebug-profile/06d049dacb2a7a3809e069c6c8289e02.cachegrind.gz
/Users/ruben/Spatie/laravel-data/.phpbench/xdebug-profile/41c9fe618b93431786fff90b1d624b82.cachegrind.gz
</code></pre><p>Running the benchmark suite now takes way longer than before. In the end, we have a new directory with our profiles:</p><ul><li>.phpbench/xdebug-profile/3fcb020036c8d7e8efdcddd4dbd66b92.cachegrind.gz</li><li>.phpbench/xdebug-profile/8deba1dcce573e1bf818772ac7a5ace0.cachegrind.gz</li><li>.phpbench/xdebug-profile/06d049dacb2a7a3809e069c6c8289e02.cachegrind.gz</li><li>.phpbench/xdebug-profile/41c9fe618b93431786fff90b1d624b82.cachegrind.gz</li></ul><p>Each file corresponds to a benchmark case within your benchmark suite. The best way to find out which file belongs to which benchmark is to look at the execution order of benchmark cases and match them with the files in the order they were created.</p><p>At the moment, all files are compressed. Let us decompress them:</p><pre><code class="language-bash">gzip --decompress .phpbench/xdebug-profile/*
</code></pre><h2 id="analyzing">Analyzing</h2><p>It is finally time to find out why our code is slow. This is also the most complicated step in the process, and there is no guide to quickly finding a bottleneck. You should have a good understanding of your code and be able to see strange behavior within profiles. Some things that might stand out:</p><ul><li>functions that shouldn&apos;t be executed</li><li>functions that are executed too much</li><li>functions that take longer than expected</li></ul><p>Let&apos;s open a profile where we create a collection of data objects:</p><figure class="kg-card kg-image-card"><img src="https://rubenvanassche.com/content/images/2023/04/qcachegrind1-1.png" class="kg-image" alt loading="lazy" width="2000" height="1182" srcset="https://rubenvanassche.com/content/images/size/w600/2023/04/qcachegrind1-1.png 600w, https://rubenvanassche.com/content/images/size/w1000/2023/04/qcachegrind1-1.png 1000w, https://rubenvanassche.com/content/images/size/w1600/2023/04/qcachegrind1-1.png 1600w, https://rubenvanassche.com/content/images/size/w2400/2023/04/qcachegrind1-1.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>First, we&apos;re going to configure QCacheGrind a bit more. Go to:</p><pre><code>QCacheGrind -&gt; Preferences -&gt; Source Annotations
</code></pre><p>And add the path where your code is situated. This allows QCacheGrind to give some hints with code examples of which code is slow.</p><figure class="kg-card kg-image-card"><img src="https://rubenvanassche.com/content/images/2023/04/qcachegrind2.webp" class="kg-image" alt loading="lazy" width="2000" height="1113" srcset="https://rubenvanassche.com/content/images/size/w600/2023/04/qcachegrind2.webp 600w, https://rubenvanassche.com/content/images/size/w1000/2023/04/qcachegrind2.webp 1000w, https://rubenvanassche.com/content/images/size/w1600/2023/04/qcachegrind2.webp 1600w, https://rubenvanassche.com/content/images/2023/04/qcachegrind2.webp 2060w" sizes="(min-width: 720px) 720px"></figure><p>The application has three panels:</p><ul><li>On the left: the functions which were called sorted by the lengthiest call</li><li>On the right-top: the callers of a selected function</li><li>On the right-bottom: for a selected function, the functios called</li></ul><p>On the left side, we see all the functions called. The Self metric shows how long it took to call a function, the Incl. metric shows how long the function and all the functions it called took. So the PHPBench function, for example, takes 99.93% of the time Incl. but it spends only 0.01% of the time within the function. All the other time is used to call other functions, our benchmarks with data creation.</p><p>Immediately we see something bizarre. We spend 24.34% of the time in <code>Illuminate\Container\Container-&gt;resolve()</code>. Let&apos;s click on that function and see what&apos;s happening:</p><figure class="kg-card kg-image-card"><img src="https://rubenvanassche.com/content/images/2023/04/qcachegrind3.png" class="kg-image" alt loading="lazy" width="2000" height="1182" srcset="https://rubenvanassche.com/content/images/size/w600/2023/04/qcachegrind3.png 600w, https://rubenvanassche.com/content/images/size/w1000/2023/04/qcachegrind3.png 1000w, https://rubenvanassche.com/content/images/size/w1600/2023/04/qcachegrind3.png 1600w, https://rubenvanassche.com/content/images/size/w2400/2023/04/qcachegrind3.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>This function has been called by <code>Illuminate\Foundation\Application-&gt;resolve()</code>. Let&apos;s click on that one. We see <code>Illuminate\Container\Container-&gt;make()</code> as the function, usually calling the <code>resolve</code> function. We keep on clicking on the most called functions until we enter the <code>helpers.php</code> file:</p><figure class="kg-card kg-image-card"><img src="https://rubenvanassche.com/content/images/2023/04/qcachegrind4.webp" class="kg-image" alt loading="lazy" width="2000" height="1001" srcset="https://rubenvanassche.com/content/images/size/w600/2023/04/qcachegrind4.webp 600w, https://rubenvanassche.com/content/images/size/w1000/2023/04/qcachegrind4.webp 1000w, https://rubenvanassche.com/content/images/size/w1600/2023/04/qcachegrind4.webp 1600w, https://rubenvanassche.com/content/images/size/w2400/2023/04/qcachegrind4.webp 2400w" sizes="(min-width: 720px) 720px"></figure><p>From this data, we can see that we&apos;re resolving a lot from the Laravel container:</p><ul><li><code>Data::from</code> 67500 items</li><li><code>DataPipeline in a closure (line 75)</code> -&gt; 225000 items</li><li><code>DataPipeline in a closure (line 69)</code> -&gt; 187500 items</li></ul><p>What&apos;s exactly happening here? Let&apos;s take a look at the <code>DataPipeline</code> code:</p><pre><code class="language-php">public function execute(): Collection
{
    /** @var \Spatie\LaravelData\Normalizers\Normalizer[] $normalizers */
    $normalizers = array_map(
        fn (string|Normalizer $normalizer) =&gt; is_string($normalizer) ? app($normalizer) : $normalizer,
        $this-&gt;normalizers
    );

    /** @var \Spatie\LaravelData\DataPipes\DataPipe[] $pipes */
    $pipes = array_map(
        fn (string|DataPipe $pipe) =&gt; is_string($pipe) ? app($pipe) : $pipe,
        $this-&gt;pipes
    );
    
    // Other code, hidden to keep things a bit easier to read
}
</code></pre><p>This code doesn&apos;t look like a performance problem. Yes, we&apos;re creating these normalizers and pipes from the container, but as long as this method isn&apos;t being executed that many times there won&apos;t be a problem in the end. This process should happen somewhere.</p><p>Let&apos;s find out from where this code is called:</p><pre><code class="language-php">public function execute(string $class, mixed ...$payloads): BaseData
{
    $properties = new Collection();

    foreach ($payloads as $payload) {
        /** @var BaseData $class */
        $pipeline = $class::pipeline();

        foreach ($class::normalizers() as $normalizer) {
            $pipeline-&gt;normalizer($normalizer);
        }

        foreach ($pipeline-&gt;using($payload)-&gt;execute() as $key =&gt; $value) {
            $properties[$key] = $value;
        }
    }
    
    // Other code, hidden to keep things a bit easier to read
}
</code></pre><p>That could be better. We create the pipeline for each data object we&apos;re constructing, and thus, we resolve all the pipes and normalizers from the container each time. Every time again, this is not required as the pipeline is statically constructed for a data object and thus the same for all objects.</p><p>For a few objects, this will be fine. But since we have collections of the same objects (sometimes more than 500), we hit a huge performance problem.</p><p>We now cache these pipelines in version 3.2 of laravel-data and add some other performance improvements. This should do a lot for our performance! Finally, let&apos;s rerun the benchmarks.</p><p>Before performance optimizations:</p><pre><code>benchData...............................I1 - Mo293.376&#x3BC;s (&#xB1;0.79%)
benchDataCollection.....................I1 - Mo5.619ms (&#xB1;0.48%)
</code></pre><p>After performance optimizations:</p><pre><code>benchData...............................I1 - Mo125.731&#x3BC;s (&#xB1;0.57%)
benchDataCollection.....................I1 - Mo2.111ms (&#xB1;0.82%)
</code></pre><p>For plain data objects, we&apos;ve improved performance by 57%, and for collections of data, by 62%. That&apos;s huge!</p><h2 id="in-the-end">In the end</h2><p>With a few hours of work, we drastically reduced the performance penalty on Flare. Profiling the most critical parts of your application can be valuable, but it is not that easy!</p><p>We keep redesigning and refactoring Flare for the next weeks and hope to launch soon! You can find a preview of our new design <a href="https://flareapp.io/blog/48-a-preview-of-our-upcoming-redesign?ref=rubenvanassche.com">here</a>. Are you interested in beta testing the new version of Flare? Please send us an email at <a>support@flareapp.io</a>.</p>]]></content:encoded></item><item><title><![CDATA[Fixing nested validation in Laravel]]></title><description><![CDATA[<p><strong>This post was first published on the Flare blog.</strong></p><p>Since the early days, Laravel includes an excellent validator class. It allows you to check if the input provided to your application is valid according a set of rules and follows a specific format. Laravel&apos;s validator works on any</p>]]></description><link>https://rubenvanassche.com/fixing-nested-validation-in-laravel/</link><guid isPermaLink="false">641ac88edf170b1f20ed5899</guid><category><![CDATA[Laravel]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Mon, 06 Mar 2023 09:21:00 GMT</pubDate><content:encoded><![CDATA[<p><strong>This post was first published on the Flare blog.</strong></p><p>Since the early days, Laravel includes an excellent validator class. It allows you to check if the input provided to your application is valid according a set of rules and follows a specific format. Laravel&apos;s validator works on any arbitrary array of data but it&apos;s typically used for request validation. You can check if an email is valid, a password is confirmed, a file is a pdf, a string is not longer than a certain amount of characters, and so much more.</p><p>Validating input in Laravel is incredibly powerful and quick to set up. It&apos;s always been one of our favorite Laravel features. But, a few strange edge cases can be found where the validator isn&apos;t working as expected. When developing a Flare feature, we encountered one of these oddities and found a nice way to work around it.</p><p>Suppose that we&apos;re trying to validate a basic array with a nested <code>team</code> property:</p><pre><code class="language-php">[
	&apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
	&apos;team&apos; =&gt; [
		&apos;id&apos; =&gt; 1,
		&apos;role&apos; =&gt; &apos;engineer&apos;
	]
]
</code></pre><p><em>(The example above is abbreviated for better readability and clarity)</em></p><p>We could now write a <a href="https://laravel.com/docs/master/validation?ref=rubenvanassche.com#form-request-validation"><code>FormRequest</code></a> to validate this data automatically or manually validate the array using the <code>Validator</code> facade in the controller. To keep things simple, we&apos;ll stick to using the validator class directly:</p><pre><code class="language-php">$rules =  [
    &apos;name&apos; =&gt; [&apos;required&apos;, &apos;string&apos;],
    &apos;team&apos; =&gt; [&apos;required&apos;, &apos;array&apos;],
    &apos;team.id&apos; =&gt; [&apos;required&apos;, Rule::exists(&apos;teams&apos;, &apos;id&apos;)],
    &apos;team.role&apos; =&gt; [&apos;required&apos;, Rule::in([&apos;engineer&apos;, &apos;project_manager&apos;, &apos;boss&apos;])],
];

$validator = Validator::make($data, $rules);
</code></pre><p>We can now check if our data array (or &quot;payload&quot;) is valid:</p><pre><code class="language-php">$validator-&gt;passes(); // Returns true, data is valid
$validator-&gt;messages(); // An empty message bag, great!
</code></pre><p>When passing an invalid payload array as our input, the validator will fail and provide us with a couple of validation messages:</p><pre><code class="language-php">$data = [
	&apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
	&apos;team&apos; =&gt; [
		&apos;id&apos; =&gt; 2023,
		&apos;role&apos; =&gt; &apos;ceo&apos;
	]
];

$validator = Validator::make($data, $rules);
$validator-&gt;passes(); // returns false!
</code></pre><p>The error bag contains the following validation messages:</p><pre><code class="language-php">[
    &quot;team.id&quot; =&gt; [&quot;The selected team.id is invalid.]
    &quot;team.role&quot; =&gt; [&quot;The selected team.role is invalid.&quot;]
]
</code></pre><p>Great, so up until now, everything is working as expected. We can validate both root level properties and nested properties in the <code>team</code> array.</p><p>Let&apos;s make things more complicated by allowing the <code>team</code> property to be <code>nullable</code>. Maybe your application allows for users without teams, so the team property can also be <code>null</code>.</p><p>Let&apos;s update our rules by adding the <code>nullable</code> rule:</p><pre><code class="language-php">[
	&apos;name&apos; =&gt; [&apos;required&apos;, &apos;string&apos;],
	&apos;team&apos; =&gt; [&apos;nullable&apos;, &apos;array&apos;],
	&apos;team.id&apos; =&gt; [&apos;required&apos;, Rule::exists(&apos;teams&apos;, &apos;id&apos;)],
	&apos;team.role&apos; =&gt; [&apos;required&apos;, Rule::in([&apos;engineer&apos;, &apos;project_manager&apos;, &apos;boss&apos;])],
]
</code></pre><p>When testing our two previous example payload again, everything still works as expected: the valid example payload passes and the second example contains errors so the validator fails. However, let&apos;s now test our new feature where we don&apos;t have a team at all:</p><pre><code class="language-php">[
	&apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
]
</code></pre><p>The <code>team</code> property is <code>nullable</code> so this payload should not give us validation errors. Let&apos;s validate:</p><pre><code class="language-php">[
    &quot;team.id&quot; =&gt; [&quot;The selected team.id is invalid.]
    &quot;team.role&quot; =&gt; [&quot;The selected team.role is invalid.&quot;]
]
</code></pre><p><em>Wait what?!</em> For some reason, the validator ran the <code>team.id</code> and <code>team.role</code> rules even though we wrote a rule that the entire <code>team</code> array is allowed be null, so what happened here?</p><p>The validator did its job correctly, albeit a bit bizarre. We wrote three rules for the <code>team</code> array. The first one tells the validator that <code>team</code> should be an array but can also be null. This rule succeeds with our last example payload. The other two rules require a value to be in a specific format. And these fail as the value is always <code>required</code>.</p><p>Laravel&apos;s validator is built in a way where each rule exists on its own. This means the two <code>team.*</code> rules have no context of the first rule indicating that the <code>team</code> array can be null. So they get executed as they would typically, resulting in two error messages.</p><p>How can we fix this?</p><h2 id="nullable-all-the-way">Nullable all the way</h2><p>We could rewrite our rules like this:</p><pre><code class="language-php">[
    &apos;name&apos; =&gt; [&apos;required&apos;, &apos;string&apos;],
    &apos;team&apos; =&gt; [&apos;nullable&apos;, &apos;array&apos;],
    &apos;team.id&apos; =&gt; [&apos;nullable&apos;, Rule::exists(&apos;teams&apos;, &apos;id&apos;)],
    &apos;team.role&apos; =&gt; [&apos;nullable&apos;, Rule::in([&apos;engineer&apos;, &apos;project_manager&apos;, &apos;boss&apos;])],
]
</code></pre><p>Now when validating the following:</p><pre><code class="language-php">[
	&apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
]
</code></pre><p>Great! No validation messages, but what if we provide a payload like this:</p><pre><code class="language-php">[
	&apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
	&apos;team&apos; =&gt; [
	    &apos;id&apos; =&gt; 1,
	]
]
</code></pre><p>Also no validation messages, but we expect one since a team always needs an id and role. There are better approaches to writing rules like this.</p><h2 id="better-requiring">Better requiring</h2><p>We could rewrite our require rules smarter by explicitly telling when a specific value is required:</p><pre><code class="language-php">[
	&apos;name&apos; =&gt; [&apos;required&apos;, &apos;string&apos;],
	&apos;team&apos; =&gt; [&apos;nullable&apos;, &apos;array&apos;],
	&apos;team.id&apos; =&gt; [&apos;required_with:team&apos;, Rule::exists(&apos;teams&apos;, &apos;id&apos;)],
	&apos;team.role&apos; =&gt; [&apos;required_with:team&apos;, Rule::in([&apos;engineer&apos;, &apos;project_manager&apos;, &apos;boss&apos;])],
]
</code></pre><p>When validating:</p><pre><code class="language-php">[
	&apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
]
</code></pre><p>No messages. Up to the next one:</p><pre><code class="language-php">[
	&apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
	&apos;team&apos; =&gt; [
	    &apos;id&apos; =&gt; 1,
	]
]
</code></pre><p>We now get the following message:</p><pre><code class="language-php">[
	&quot;team.role&quot; =&gt; [&quot;The team.role field is required when team is present.&quot;]
]
</code></pre><p>Marvelous, we succeeded in writing the correct rules! But from now on, we must always use the <code>required_with:team</code> rule instead of the <code>required</code> rule and remember this for new properties to be added in the future.</p><p>We also need to be careful. If someone decides to change the name of the team property, then all our rules need to be updated. We only have two rules now, but as an application grows, these numbers tend to rise rapidly, and a mistake is quickly made.</p><p>Lastly, what if we also want to nest another array within the team array like the payload bellow, where we also want to be able to (optionally) disable or enable extra features per team:</p><pre><code class="language-php">[
    &apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
    &apos;team&apos; =&gt; [
        &apos;id&apos; =&gt; 1,
        &apos;role&apos; =&gt; &apos;engineer&apos;,
        &apos;features&apos; =&gt; [
            &apos;github&apos; =&gt; true,
            &apos;jira&apos; =&gt; false,
        ]
    ]
]
</code></pre><p>And to make things more complicated, you should also be able to provide no features (in this case, defaults could be used):</p><pre><code class="language-php">[
    &apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
    &apos;team&apos; =&gt; [
        &apos;id&apos; =&gt; 1,
        &apos;role&apos; =&gt; &apos;engineer&apos;,
    ]
]
</code></pre><p>These were the best rules I could come up with:</p><pre><code class="language-php">[
    &apos;name&apos; =&gt; [&apos;required&apos;, &apos;string&apos;],
    &apos;team&apos; =&gt; [&apos;nullable&apos;, &apos;array&apos;],
    &apos;team.id&apos; =&gt; [&apos;required_with:team&apos;, Rule::exists(&apos;teams&apos;, &apos;id&apos;)],
    &apos;team.role&apos; =&gt; [&apos;required_with:team&apos;, Rule::in([&apos;engineer&apos;, &apos;project_manager&apos;, &apos;boss&apos;])],
    &apos;team.features&apos; =&gt; [&apos;nullable&apos;, &apos;required_with:team&apos;, &apos;array&apos;],
    &apos;team.features.github&apos; =&gt; [&apos;required_with:team.features&apos;, &apos;boolean&apos;],
    &apos;team.features.jira&apos; =&gt; [&apos;required_with:team.features&apos;, &apos;boolean&apos;],
];
</code></pre><p>But these are not 100% watertight since the latest payload we&apos;v constructed without the features array will result in the following validation message:</p><pre><code class="language-php">[
	&quot;team.features&quot; =&gt; [
	  &quot;The team.features field is required when team is present.&quot;
	]
]
</code></pre><p>The <code>required_with</code> rules can fix our problem but have a few disadvantages. Is there another way?</p><h2 id="fixing-the-issue-once-and-for-all">Fixing the issue once and for all</h2><p>In Flare, we don&apos;t write validation rules ourselves. One of our packages called <a href="https://github.com/spatie/laravel-data?ref=rubenvanassche.com">laravel-data</a> automatically generates them.</p><p>Let&apos;s revisit our previous example, but we now structure our data using data objects. Which has as an added benefit a stricter type structure we can use in the backend. We stop using untyped arrays with data and have objects with typed properties.</p><p>The laravel-data package can also generate TypeScript definitions based upon the types within our data objects, so our frontend code is now also typed exactly like our backend. Cool!</p><p>These are the data objects we&apos;ve created based on the rules we decided on earlier:</p><pre><code class="language-php">class UserData extends Data
{
    public function __construct(
        public string $name,
        public ?TeamData $team,
    ) {
    }
}

class TeamData extends Data
{
    public function __construct(
        #[Exists(&apos;teams&apos;, &apos;id&apos;)]
        public int $id,
        #[In(&apos;engineer&apos;, &apos;project_manager&apos;, &apos;boss&apos;)]
        public string $role,
        public ?TeamFeaturesData $features,
    ) {
    }
}

class TeamFeaturesData extends Data
{
    public function __construct(
        public bool $github,
        public bool $jira,
    ) {
    }
}
</code></pre><p>Within our controller, we inject the <code>User</code> data object as follows:</p><pre><code class="language-php">class UpdateUserController
{
    public function __invoke(
        User $user,
        UserData $data,
        UpdateOrCreateUserAction $updateOrCreateUserAction
    )
    {
        $updateOrCreateUserAction($data, $user);

        flash(&apos;User updated!&apos;);

        return redirect()-&gt;back();
    }
}
</code></pre><p>As you can see, we also inject the user model we&apos;re updating and the action (a class with business logic) to perform the update within the database.</p><p>Due to the nature of laravel-data, when injected in a controller, the data object will validate the request input before creating itself. Let&apos;s take a look at the previous inputs we provided to the validator:</p><pre><code class="language-php">[
    &apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
];
</code></pre><p>No validation messages &#x1F44D;</p><pre><code class="language-php">[
    &apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
    &apos;team&apos; =&gt; [
        &apos;id&apos; =&gt; 1,
        &apos;role&apos; =&gt; &apos;engineer&apos;,
    ],
]
</code></pre><p>Also no validation messages &#x1F973;</p><pre><code class="language-php">[
    &apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
    &apos;team&apos; =&gt; [
        &apos;id&apos; =&gt; 1,
        &apos;role&apos; =&gt; &apos;engineer&apos;,
        &apos;features&apos; =&gt; [
            &apos;github&apos; =&gt; true,
            &apos;jira&apos; =&gt; false,
        ],
    ],
]
</code></pre><p>No validation messages. We fixed our initial problem! &#x1F389;</p><p>At this point in time, you start to wonder, is the validation working? Let&apos;s try this one out:</p><pre><code class="language-php">[
    &apos;name&apos; =&gt; &apos;Ruben Van Assche&apos;,
    &apos;team&apos; =&gt; [
        &apos;id&apos; =&gt; 1,
    ],
]

// Validation messages:

[
    &quot;team.role&quot; =&gt; [&quot;The team.role field is required when team is present.&quot;]
]
</code></pre><p>It looks like everything is still validated as expected.</p><h2 id="internals">Internals</h2><p>Let&apos;s go quickly over the internals of laravel-data. Until a few weeks ago, laravel-data worked precisely like our first approach by making all rules nullable in nested arrays. Though working, there were better solutions than this. So we completely rewrote the validation logic for laravel-data&apos;s third version.</p><p>Instead of generating rules before the payload is provided, laravel-data has a sort of JIT rule generator. It will generate rules based on the definition in the data class and the payload passed in.</p><p>For example, when we have a data object like <code>UserData</code>, all non-nested data property rules will be generated. The nested <code>TeamData</code> is nullable, so it will only start doing the same and thus generate rules for itself when we find a key <code>team</code> within the input payload that is not null.</p><p>If you&apos;re more interested in the internal workings of the validation rule generation, then take a look over <a href="https://github.com/spatie/laravel-data/blob/main/src/Resolvers/DataValidationRulesResolver.php?ref=rubenvanassche.com">here</a>, where we generate validation rules for data objects just in time.</p><h2 id="closing">Closing</h2><p>Laravel&apos;s validator is incredible, but sometimes it needs some help. Expect more articles like this on the Flare blog in the coming months and a new coat of paint for our website and application. Want a sneak peek? Mail us at <a>support@flareapp.io</a>.</p>]]></content:encoded></item><item><title><![CDATA[Grouping SQL errors]]></title><description><![CDATA[Flare gets a lot of errors, but how to group them?]]></description><link>https://rubenvanassche.com/grouping-sql-errors/</link><guid isPermaLink="false">641ac800df170b1f20ed588c</guid><category><![CDATA[Laravel]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Sun, 28 Nov 2021 09:19:00 GMT</pubDate><content:encoded><![CDATA[<p><strong>This post first appeared on the Flare blog.</strong></p><p>Flare gets a lot of errors every day. To keep a good overview, we try to group them as best as possible because you don&apos;t want to end with thousands of errors in your Flare project when there&apos;s only one bug within your codebase.</p><p>For most errors, grouping is relatively easy. We look at the top frame within the call stack where the error occurred and keep track of the class, method and line where the error occurred. When Flare receives another error, we again look at this top frame and group it by these parameters.</p><p>This method works pretty well for most exceptions thrown within PHP. But there are cases where this approach isn&apos;t giving the best results.</p><h2 id="sql-exceptions">SQL Exceptions</h2><p>A Laravel application will throw a <code>QueryException</code> whenever something goes wrong with your database. When we would group by top frame, all database errors would be grouped into a single item. That&apos;s something we don&apos;t want since those SQL errors could be entirely different.</p><p>We could not use the top frame of the call stack. But go deeper in the stack until we find a frame within your application. But what then if an external package caused the exception? It could be that we never reach an application frame. Also, the deeper we go through the call stack, the more general the path of execution becomes. We could be grouping errors that have nothing to do with each other.</p><p>Flare uses an alternative solution. We do not group by top frame in cases of a <code>QueryException</code> but by exception class and message. Now, these two errors become two different items within Flare:</p><pre><code class="language-sql">SQLSTATE[42S22]: Column not found: 1054 Unknown column &apos;titl&apos; in &apos;field list&apos; (SQL: SELECT * FROM `posts` WHERE `titl` = &apos;flare groupings&apos;)
</code></pre><pre><code class="language-sql">SQLSTATE[42S22]: Column not found: 1054 Unknown column &apos;publishe&apos; in &apos;field list&apos; (SQL: UPDATE `posts` SET `publishe` = 1 WHERE `id` = 1)
</code></pre><p>Great! But what happens to the grouping when we run this piece of code, and it fails?</p><pre><code class="language-php">public function publish(Post $post, bool $published): void
{
	$post-&gt;update([&apos;publishe&apos; =&gt; $published]);
}
</code></pre><p>The answer isn&apos;t that clear. It depends since a post can have multiple id&apos;s and <code>published</code> can be <code>0</code> or <code>1</code>. The following two exceptions could be thrown when this piece of code fails:</p><pre><code class="language-sql">SQLSTATE[42S22]: Column not found: 1054 Unknown column &apos;publishe&apos; in &apos;field list&apos; (SQL: UPDATE `posts` SET `publishe` = 0 WHERE `id` = 1)
</code></pre><pre><code class="language-sql">SQLSTATE[42S22]: Column not found: 1054 Unknown column &apos;publishe&apos; in &apos;field list&apos; (SQL: UPDATE `posts` SET `publishe` = 1 WHERE `id` = 1)
</code></pre><p>This will cause Flare to create two entries for errors that should be grouped since they represent the same bug. We want to avoid these scenarios at all costs!</p><p>Let&apos;s take a look at the two queries:</p><pre><code class="language-sql">UPDATE `posts` SET `publishe` = 0 WHERE `id` = 1
</code></pre><pre><code class="language-sql">UPDATE `posts` SET `publishe` = 1 WHERE `id` = 1
</code></pre><p>When we would use prepared statements with bindings where we sent the values separate from the query to the database server then, those queries would look like this:</p><pre><code class="language-sql">UPDATE `posts` SET `publishe` = ? WHERE `id` = ?
</code></pre><pre><code class="language-sql">UPDATE `posts` SET `publishe` = ? WHERE `id` = ?
</code></pre><p>Great! The two queries are identical. We can now group using the exception class: <code>QueryException</code> and this query with bindings. Sadly enough, often prepared statements are entirely ignored, so we cannot use them.</p><p>But what if we could strip all values from these queries to look like queries with bindings?</p><h2 id="parsing-queries">Parsing queries</h2><p>SQL queries are complicated, we&apos;ve seen some simple queries earlier, but they can become quite complex. That&apos;s why we&apos;ll use a <a href="https://github.com/greenlion/PHP-SQL-Parser?ref=rubenvanassche.com">SQL parser</a> to parse the queries into tokens so we know what should be replaced and whatnot.</p><p>The first thing we need to do is parse the query:</p><pre><code class="language-php">(new PHPSQLParser($query))-&gt;parsed;
</code></pre><p>Such a parsed query looks like this:</p><pre><code class="language-php">array:3 [
  &quot;UPDATE&quot; =&gt; array:1 [
    0 =&gt; array:10 [
      &quot;expr_type&quot; =&gt; &quot;table&quot;
      &quot;table&quot; =&gt; &quot;`posts`&quot;
      &quot;no_quotes&quot; =&gt; array:2 [
        &quot;delim&quot; =&gt; false
        &quot;parts&quot; =&gt; array:1 [
          0 =&gt; &quot;posts&quot;
        ]
      ]
      &quot;alias&quot; =&gt; false
      &quot;hints&quot; =&gt; false
      &quot;join_type&quot; =&gt; &quot;JOIN&quot;
      &quot;ref_type&quot; =&gt; false
      &quot;ref_clause&quot; =&gt; false
      &quot;base_expr&quot; =&gt; &quot;`posts`&quot;
      &quot;sub_tree&quot; =&gt; false
    ]
  ]
  &quot;SET&quot; =&gt; array:1 [
    0 =&gt; array:3 [
      &quot;expr_type&quot; =&gt; &quot;expression&quot;
      &quot;base_expr&quot; =&gt; &quot;`publishe` = 0&quot;
      &quot;sub_tree&quot; =&gt; array:3 [
        0 =&gt; array:4 [
          &quot;expr_type&quot; =&gt; &quot;colref&quot;
          &quot;base_expr&quot; =&gt; &quot;`publishe`&quot;
          &quot;no_quotes&quot; =&gt; array:2 [
            &quot;delim&quot; =&gt; false
            &quot;parts&quot; =&gt; array:1 [
              0 =&gt; &quot;publishe&quot;
            ]
          ]
          &quot;sub_tree&quot; =&gt; false
        ]
        1 =&gt; array:3 [
          &quot;expr_type&quot; =&gt; &quot;operator&quot;
          &quot;base_expr&quot; =&gt; &quot;=&quot;
          &quot;sub_tree&quot; =&gt; false
        ]
        2 =&gt; array:3 [
          &quot;expr_type&quot; =&gt; &quot;const&quot;
          &quot;base_expr&quot; =&gt; &quot;0&quot;
          &quot;sub_tree&quot; =&gt; false
        ]
      ]
    ]
  ]
  &quot;WHERE&quot; =&gt; array:3 [
    0 =&gt; array:4 [
      &quot;expr_type&quot; =&gt; &quot;colref&quot;
      &quot;base_expr&quot; =&gt; &quot;`id`&quot;
      &quot;no_quotes&quot; =&gt; array:2 [
        &quot;delim&quot; =&gt; false
        &quot;parts&quot; =&gt; array:1 [
          0 =&gt; &quot;id&quot;
        ]
      ]
      &quot;sub_tree&quot; =&gt; false
    ]
    1 =&gt; array:3 [
      &quot;expr_type&quot; =&gt; &quot;operator&quot;
      &quot;base_expr&quot; =&gt; &quot;=&quot;
      &quot;sub_tree&quot; =&gt; false
    ]
    2 =&gt; array:3 [
      &quot;expr_type&quot; =&gt; &quot;const&quot;
      &quot;base_expr&quot; =&gt; &quot;1&quot;
      &quot;sub_tree&quot; =&gt; false
    ]
  ]
]
</code></pre><p>We only want to replace the constants within the query, so we update each token where the <code>expr_type</code> is <code>const</code> to not use a value like <code>1</code> or <code>0</code> but to use a <code>?</code> just like with the bindings in prepared statements:</p><pre><code class="language-php">array:3 [
  &quot;expr_type&quot; =&gt; &quot;const&quot;
  &quot;base_expr&quot; =&gt; &quot;1&quot;
  &quot;sub_tree&quot; =&gt; false
]
</code></pre><p>Becomes:</p><pre><code class="language-php">array:3 [
  &quot;expr_type&quot; =&gt; &quot;const&quot;
  &quot;base_expr&quot; =&gt; &quot;?&quot;
  &quot;sub_tree&quot; =&gt; false
]
</code></pre><p>The cool thing with this parser package is that we can convert these tokens back to a SQL query:</p><pre><code class="language-php">(new PHPSQLCreator())-&gt;create($parsed);
</code></pre><p>And we&apos;re done! Our query now looks like this:</p><pre><code class="language-sql">UPDATE `posts` SET `publishe` = ? WHERE `id` = ?
</code></pre><p>Within Flare, the algorithm of stripping the token tree is a bit more complex than we show here, but the gist is the same. Take some raw values and replace them with question marks.</p><h2 id="more-exotic-queries">More exotic queries</h2><p>But this is not where we end, although the parser is quite good at parsing the wildest queries. Sometimes it just cannot understand what&apos;s happening. For example:</p><pre><code class="language-sql">UPDATE users SET uuid = 89637150-f807-11ea-aee4-51fe58f2b2b3
</code></pre><p>In some database configurations, this query is valid. And our parser will only strip the first part of the UUID because it thinks the <code>-</code> signs are operators, and the other parts of the UUID are references to columns within your table:</p><pre><code class="language-sql">UPDATE users SET uuid = ? - f807 - 11ea - aee4 - 51fe58f2b2b3
</code></pre><p>The same thing happens with dates:</p><pre><code class="language-sql">UPDATE users SET date = ? - ? - 2020T17:24:34.012345Z
</code></pre><p>That&apos;s why, before we parse the query, we preprocess it by stripping out some well-known types of strings like dates, emails, UUIDs:</p><pre><code class="language-php">preg_replace(
	[
		&apos;/\S+@\S+\.\S+/&apos;, // emails
		&apos;/\d{2,4}-\d{2}-\d{2,4}[ T]?(\d{2}:\d{2}:\d{2}([+-]\d{2}:\d{2})?(\.\d{6}Z)?)?/&apos;, // dates
		&apos;/[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}/&apos;, // uuids
	],
	[&apos;?&apos;, &apos;?&apos;, &apos;?&apos;],
	$query
);
</code></pre><p>Preprocessing makes the queries a bit more readable for our parser, which improves our final result drastically!</p><h2 id="connection-issues">Connection issues</h2><p>A <code>QueryException</code> always looks like this:</p><pre><code class="language-sql">SQLSTATE[42S22]: Column not found: 1054 Unknown column &apos;publishe&apos; in &apos;field list&apos; (SQL: UPDATE `posts` SET `publishe` = 0 WHERE `id` = 1)
</code></pre><p>Did you notice the <code>SQLSTATE[42S22]</code> part? It is a <a href="https://en.wikipedia.org/wiki/SQLSTATE?ref=rubenvanassche.com">code</a> telling you what went wrong with your database. For example, a connection issue in a MySql database has the following code: [HY000][2002] looks familiar?</p><p>Within Flare, we&apos;ll also take a look at these codes. When we notice the error is caused by connection issues and not a bug within your code, then we group all these errors since your database server was probably just a few moments down.</p><h2 id="impossible-queries">Impossible queries</h2><p>Let&apos;s take a look at a final example:</p><pre><code class="language-sql">SELECT * FROM invoices WHERE number = 123ABC
</code></pre><p>Such a query is a difficult one. The parser doesn&apos;t know if <code>123ABC</code> is a constant or a reference to a column within your database table.</p><p>In the latter case, we don&apos;t want to replace the column name with a <code>?</code> since these are not values that change between exceptions.</p><p>In such cases, we do not strip the value and keep that part of the query.</p>]]></content:encoded></item><item><title><![CDATA[Laravel resource links two years later]]></title><description><![CDATA[Today we're abandoning spatie/laravel-resource-links. This was the first PHP package I've build and I'm still kinda proud of it but sometimes it is better to say goodbye.]]></description><link>https://rubenvanassche.com/laravel-resource-links-two-years-later/</link><guid isPermaLink="false">6076fbca05b81504b0748333</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Wed, 14 Apr 2021 14:29:33 GMT</pubDate><content:encoded><![CDATA[<p>The package provided some helper functions you could use within your Laravel Resources to add urls to parts of your application. For example, let&apos;s say we have a <code>UserController</code> and a <code>UserResource</code> for a <code>User</code> model:</p><p></p><pre><code class="language-php">class UserResource extends JsonResource
{
    use HasLinks, HasMeta;
    
    public function toArray($request): array
    {
        return [
            &apos;id&apos; =&gt; $this-&gt;id,
            &apos;name&apos; =&gt; $this-&gt;name,
            &apos;email&apos; =&gt; $this-&gt;email,
            &apos;links&apos; =&gt; $this-&gt;links(UsersController::class),
        ];
    }

    public static function meta()
    {
        return [
            &apos;links&apos; =&gt; self::collectionLinks(UsersController::class),
        ];
    }
}
</code></pre><p>In a response this would become the following Json:</p><p></p><pre><code class="language-php">{
   &quot;data&quot;:[
      {
         &quot;id&quot;:1,
         &quot;name&quot;: &quot;Ruben Van Assche&quot;,
         &quot;email&quot;: &quot;ruben@spatie.be&quot;
         &quot;links&quot;: {
            &quot;show&quot;: &quot;https://laravel.app/users/1&quot;,
            &quot;edit&quot;: &quot;https://laravel.app/users/1/edit&quot;,
            &quot;update&quot;: &quot;https://laravel.app/users/1&quot;,
            &quot;delete&quot;: &quot;https://laravel.app/users/1&quot;
         }
      }
   ],
   &quot;meta&quot;: {
      &quot;links&quot;: {
         &quot;index&quot;: &quot;https://laravel.app/users&quot;,
         &quot;create&quot;: &quot;https://laravel.app/users/create&quot;,
         &quot;store&quot;:  &quot;https://laravel.app/users&quot;
      }
   }
}

</code></pre><p>It was actually a url generator for lazy developers.</p><h2 id="why-abandoning-the-package">Why abandoning the package?</h2><p>Allthoug the package still works, and I&apos;ll guess as long as Laravel doesn&apos;t change too much about the routing systsem it will still work for quite a while we decided to stop the development.</p><p>Originally this package was built for one project, and it is still being used there. But since then we&apos;ve created many (much bigger) projects then that one project and tought we could use this package. But we didn&apos;t.</p><p>The idea initially sounded great, never write links in resources again. But it is actually not that much work to write links, if we rewrite the above resource for example:</p><p></p><pre><code class="language-php">class UserResource extends JsonResource
{
    use HasLinks, HasMeta;

    public function toArray($request): array
    {
        return [
            &apos;id&apos; =&gt; $this-&gt;id,
            &apos;name&apos; =&gt; $this-&gt;name,
            &apos;email&apos; =&gt; $this-&gt;email,
            &apos;links&apos; =&gt; [
                &quot;show&quot; =&gt; action([UsersController::class, &apos;show&apos;], $this),
                &quot;edit&quot; =&gt; action([UsersController::class, &apos;edit&apos;], $this),
                &quot;update&quot; =&gt; action([UsersController::class, &apos;update&apos;], $this),
                &quot;delete&quot; =&gt; action([UsersController::class, &apos;delete&apos;], $this),
            ],
        ];
    }

    public static function meta()
    {
        return [
            &apos;links&apos; =&gt; [
                &quot;index&quot; =&gt; action([UsersController::class, &apos;index&apos;]),
                &quot;create&quot; =&gt; action([UsersController::class, &apos;create&apos;]),
                &quot;store&quot; =&gt; action([UsersController::class, &apos;store&apos;]),
            ]
        ];
    }
}
</code></pre><p>That&apos;s not so bad, it&apos;s a bit more code but a lot more readable. Adding or removing certain links from a resource is now super easy. Do you want to add an array with some extra endpoints within the links property? Not a problem! Do you want use other names for the link properties? Change them! Do you want to add an action to another controller? Do it!</p><p>Although the package supported all these features, they weren&apos;t always that easy to use. Almost always when adding links you had to check the docs if you wanted to do something little more complicated than just adding a link to a controller.</p><p>That brings us to the next reason for abandonment, magic! Automatically generated links sounded great in the beginning but they are a big black magic box you don&apos;t want to open. Most of the time the package worked as expected. But there were cases where it didn&apos;t and debugging those cases was hard. Especially those cases where you had resources within resources, the horrors!</p><p>The package replaced some parts of the Laravel routing system so it could plug into it and also did some crazy stuff with the Laravel Resources, they actually weren&apos;t built for this kind of stuff.</p><p>I haven&apos;t touched the code for two years and I actually have no idea what it does and how it works. There is a class <code>ParameterResolver</code> that&apos;s a 130 lines, it tries to find parameters for constructing the links and I&apos;m really scared of it. That&apos;s not a good sign.</p><p>My first though was to write a second version, a more light version of the package. But that would still require a lot of crazy code to interface with Laravel. Why would we?</p><h2 id="conclusion">Conclusion</h2><p>That&apos;s it, farewell laravel-resource-links, you were the first package I&apos;ve ever made, since then I&apos;ve learned so much about package development, but your time has come.</p><p>If you don&apos;t want to manually change all your links within your application take a look at <a href="https://github.com/tighten/ziggy?ref=rubenvanassche.com">Ziggy</a> it works a bit different then laravel-resource-links but also has the ability to automatically generate links.</p>]]></content:encoded></item><item><title><![CDATA[Speeding up the Spatie package installation workflow 🐟]]></title><description><![CDATA[spatie/one-package-to-rule-them-all is probably the biggest package we've ever built at Spatie]]></description><link>https://rubenvanassche.com/speeding-up-the-spatie-package-installation-workflow/</link><guid isPermaLink="false">6065b30c05b81504b0748305</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Thu, 01 Apr 2021 11:59:21 GMT</pubDate><content:encoded><![CDATA[<p>Today we&apos;re introducing a package that&apos;s a real gamechanger. This one will probably speed your current workflow by 200%. Let&apos;s take a look at it!</p><p><a href="https://github.com/spatie/one-package-to-rule-them-all?ref=rubenvanassche.com">spatie/one-package-to-rule-them-all</a> is probably the biggest package we&apos;ve ever built at Spatie. That&apos;s because it includes every package built at Spatie to date.</p><blockquote>The coffee breaks required when composer was installing new dependencies are gone</blockquote><p>Over are the days, you had to add a Spatie package to composer manually. Now, you include <a href="https://github.com/spatie/one-package-to-rule-them-all?ref=rubenvanassche.com">spatie/one-package-to-rule-them-all</a> in your project, and you have all the Spatie packages available to you. It&apos;s not possible anymore to make a mistake between `installing` and `requiring` a composer package. The coffee breaks required when composer was installing new dependencies are gone, productivity will rise again.</p><h2 id="how-does-it-work">How does it work?</h2><p>The most crucial file within the package is the <code>composer.json</code>. I think we really did something groundbreaking here. Usually, we&apos;re thinking hours about the required dependencies a package needs to find the most minimal set of dependencies. This time we decided to take a different approach. Why add only a few dependencies if we could add every Spatie package available?</p><p>Though the idea is simple, the execution was absolute hell. Some of our older packages require PHP 7 or even PHP 5.6 <em>ewww. </em>We wanted to make this package a first-class PHP 8 citizen, but that would mean we couldn&apos;t include the older packages with unsupported PHP versions.</p><p>Luckily after a few long coffee breaks with the whole team, we&apos;ve found the solution. Instead of requiring these older packages, we can simply suggest them to you if you want to install them.</p><p>I think this package is going to change the Laravel landscape forever. Try it out by once and for all installing the package:</p><p></p><pre><code class="language-bash">composer require spatie/one-package-to-rule-them-all
</code></pre><p>And let us know what you think!</p><p>PS: do not forget to run the <code>php artisan package:inspire</code> command</p>]]></content:encoded></item><item><title><![CDATA[A better way to reference things in your application]]></title><description><![CDATA[We constantly reference things within our application with numbers and strings, but is there a better way?]]></description><link>https://rubenvanassche.com/a-better-way-to-reference-things-in-your-application/</link><guid isPermaLink="false">6041f1709ba20c049b1da088</guid><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Fri, 12 Mar 2021 08:46:37 GMT</pubDate><content:encoded><![CDATA[<p>We, as programmers, use id&apos;s continuously throughout our day and probably would be lost without them. An id can be a simple integer that increases when a new record is inserted into a database. It can also &#xA0;be as complicated as a UUID which is more or less unique at the time of generation.</p><p>In the long run, id&apos;s have only one practical use: identifying things, and with things, I mean everything. An id can be used to represent a user in your database. A drive on your computer. Or a transaction from your bank. An id can represent god knows what resource.</p><p>Id&apos;s can identify everything around us, which&apos;s its strongest point and its greatest downfall. Do you know what id = <code>5</code> or UUID = <code>00145851-4612-4513-82ca-68b77df0a0b0</code> represents? Though the id can point to a specific resource, it does not tell you what kind of resource it is.</p><p>Stripe solves this by prepending the id&apos;s with a type it is representing. A customer id will always start with <code>cus_</code>, and a charge always has <code>ch_</code> prefixed. The id does not only identify a specific resource. It also tells you what kind of resource it is.</p><p>I&apos;m in love with this concept. Every time you&apos;re working with id&apos;s within your application. You know the kind of resources you&apos;re working with. But wouldn&apos;t it be cool if your programming language and IDE also knows which resources these id&apos;s represent?</p><p>Let&apos;s take a look at a simple blogging application where a user can comment on a post. I&apos;m using Laravel in this example, but you can use this method in every framework or programming language.</p><p>At some point you want to write a piece of code to add a new comment to a post, the signature of such piece of code may look like this:</p><pre><code class="language-php">public function addComment(
	int $userId,
	int $postId,
	string $content
): Comment;
</code></pre><p>Now comes the tricky part, what if within a few months we decide to switch the order of the id&apos;s because it feels more natural:</p><pre><code class="language-php">public function addComment(
	int $postId,
	int $userId,
	string $content
): Comment;
</code></pre><p>We&apos;ll need to update all these method calls throughout our codebase, so the code keeps working. What if we forget one? At some point, the id of a post will be given as user id and vice versa. These kind of errors are difficult to spot and take a lot of time debugging.</p><p>There are solutions to this problem, we could for example always use models as a parameter to the function:</p><pre><code class="language-php">public function addComment(
	Post $post,
	User $user,
	string $content
): Comment;
</code></pre><p>When we provide a <code>User</code> model as the first parameter, PHP will complain because the types are not the same. We get an exception and don&apos;t need to debug for hours to find the problem, neat!</p><p>But, we always need to have the <code>Post</code> and <code>User</code> models loaded when we want to call this method. <a href="https://freek.dev/1364-ever-wondered-what-hydrate-means?ref=rubenvanassche.com">Hydrating</a> models can be quite resourceful, especially when you&apos;ve got a lot of models. What if we want to represent id&apos;s not tied to models, such as the Stripe resources we saw earlier? We don&apos;t have models for each Stripe charge or customer, so we cannot use them as a type for our method.</p><p>Let&apos;s go back to where we started. The only thing we want is typing our id&apos;s so we can pass them to functions correctly. What if we put these id&apos;s, the strings and integers in their own object? It&apos;s a little bit of both worlds, we don&apos;t need models and can represent non-models, but we keep the strong typing:</p><pre><code class="language-php">class PostId implements Stringable{		
	public function __construct(
		private int $id
	){}
	
	public function id(): int
    {
		return $this-&gt;id;
	}
}
</code></pre><p>When we also create such id for the <code>User</code> model we can now refactor out initial piece of code to this:</p><pre><code class="language-php">public function addComment(
	PostId $postId,
	UserId $userId,
	string $content
): Comment;
</code></pre><p>Great, passing a <code>UserId</code> as a <code>PostId</code> will cause an exception! Since we&apos;ll have a lot of these id classes, let&apos;s create a base class for them:</p><pre><code class="language-php">abstract class Id implements Stringable{
	public static function create(int $id): static
    {
		return new static($id);
	}

	public function __construct(
		private int $id
	){}
	
	public function id(): int
    {
		return $this-&gt;id;
	}
	
	public function equals(int|Id $other): bool
    {
		return (string)$this === (string)$other;
	}
	
    public function __toString(): string
    {
        return $this-&gt;id;
    }
}
</code></pre><p>Now we let <code>PostId</code> and <code>UserId</code> extend from this <code>Id</code> class, which gives us the ability to create a new id and easily compare them.</p><p>Since these id&apos;s represent models, we can take this even a bit further in a Laravel application. Let&apos;s create yet another abstract class: <code>ModelClass</code>:</p><pre><code class="language-php">abstract class ModelId extends Id{
    abstract protected function getModelClass(): string;
    
	public function __construct(
		private int $id
	){}

    public function model(): Model
    {
        return $this-&gt;modelClass::find($this-&gt;id);
    }
    
    public function exists(): bool
    {
    	return $this-&gt;model() !== null;
    }
}
</code></pre><p>We let <code>PostId</code> and <code>UserId</code> extend from <code>ModelId</code> and need to implement the abstract function <code>getModelClass</code>:</p><pre><code class="language-php">class PostId implements Stringable{		
	public function getModelClass(): string
    {
		return Post::class;
	}
}
</code></pre><p>Now we can do the following:</p><pre><code class="language-php">$postId = PostId::create(10);
</code></pre><p>When we can easily check if the model exists:</p><pre><code class="language-php">$postId-&gt;exists(); // true
</code></pre><p>Or get the model itself:</p><pre><code class="language-php">$postId-&gt;model(); // Post object
</code></pre><p>This is only useful for id&apos;s representing models. The Stripe id&apos;s we talked about in the beginning would only extend from the <code>Id</code> class since they&apos;re not models.</p><p>We don&apos;t have to stop here! In our project, we&apos;ve added some extra functionality to these abstract classes like:</p><ul><li>creating multiple id&apos;s in one go</li><li>the ability to pass null to the create function, which will return null instead of an <code>Id</code> object</li><li>a simple helper to get the morph class from a <code>ModelId</code></li></ul><p>Let&apos;s go full circle. We can easily make a new <code>Id</code> with the <code>create</code> method, but wouldn&apos;t it be cool if we could get the <code>Id</code> from a model like this:</p><pre><code class="language-php">$post-&gt;getId();
</code></pre><p>We create a new interface which the <code>Post</code> and <code>User</code> models will implement:</p><pre><code class="language-php">interface WithId{
	public function getId(): Id
}
</code></pre><p>Now we add this interafce to our models as such:</p><pre><code class="language-php">class Post extends Model implements WithId{
	public function getId(): Id
    {
		return new PostId($this-&gt;id);
	}
}
</code></pre><p>And we&apos;re done! As a bonus, since our <code>Id</code> class is <code>Stringable</code>, we can still use the Eloquent functions that Laravel provides, for example, this will work:</p><pre><code class="language-php">$postId = PostId::create(20);

$post = Post::findOrFail($postId); //Post object
</code></pre><p>When you&apos;re writing big applications, encapsulating id&apos;s can make your code a lot more readable for everyone working on the project. Give it a try in your next project and let me know what you think about it!</p>]]></content:encoded></item><item><title><![CDATA[Switching between PHP versions with Laravel Valet]]></title><description><![CDATA[Laravel Valet can easily switch between PHP versions, but sometimes it can get stuck. Let's fix that!]]></description><link>https://rubenvanassche.com/switching-between-php-versions-with-laravel-valet/</link><guid isPermaLink="false">5ff80e4a7329bb04b1d6c63b</guid><category><![CDATA[Laravel]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Fri, 08 Jan 2021 08:26:45 GMT</pubDate><content:encoded><![CDATA[<p>Since PHP8 was released we&apos;ve started upgrading all our projects to PHP 8, the things we can do with attributes, union types and other new features made us desire to do this upgrade as quickly as possible.</p><p>But sometimes you&apos;ve got to work on an older project. When you want to switch between PHP versions in your terminal and <a href="https://laravel.com/docs/master/valet?ref=rubenvanassche.com">Laravel Valet</a> it is only one terminal command away:</p><pre><code class="language-bash">valet use php@7.4 --force</code></pre><p></p><p>Want to switch back to PHP 8 then use php@8.0. Or just php since it&apos;s the most recent released PHP version at this moment. You can switch to any PHP version installed with Homebrew on your system:</p><pre><code class="language-bash">valet use php@8.0 --force
valet use php@7.4 --force
valet use php@7.3 --force
valet use php@7.2 --force
valet use php@7.1 --force
valet use php@7.0 --force
valet use php@5.6 --force</code></pre><p></p><p>You&apos;ll probably need the <a href="https://github.com/shivammathur/homebrew-php?ref=rubenvanassche.com">shivammathur/homebrew-php</a> tap to install some of these older versions with Homebrew.</p><h2 id="fixing-a-stuck-valet-php-version">Fixing a stuck Valet PHP version</h2><p>Sometimes, the <code>valet use</code> command only switches the PHP&apos;s terminal version, but Valet&apos;s PHP version keeps stuck. It can be that the valet socket is not configured properly, removing it normally solves the problem:</p><pre><code class="language-bash">rm ~/.config/valet/valet.sock
valet restart</code></pre>]]></content:encoded></item><item><title><![CDATA[Automatically generating your Laravel morph map]]></title><description><![CDATA[Make Laravel morph maps fun again!]]></description><link>https://rubenvanassche.com/automatically-generating-your-laravel-morph-map/</link><guid isPermaLink="false">5fa551f69db05b047f926ce0</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Mon, 16 Nov 2020 15:36:16 GMT</pubDate><content:encoded><![CDATA[<p>Polymorphic relations in Laravel are a very cool <a href="https://laravel.com/docs/8.x/eloquent-relationships?ref=rubenvanassche.com#polymorphic-relationships">feature</a>! You can create a model like <code>Comment</code> and link it to models like <code>Post</code> and <code>Article</code>. Such a link can be made by adding the fields <code>commentable_id</code> and <code>commentable_type</code> to the <code>Comment</code> model.</p><p>When we want to link a comment to a post, we have to create a new <code>Comment</code> model with <code>commentable_id</code> equal to the id of the post and <code>commentable_type</code> equal to <code>App\Post</code>, the class of the post.</p><p>But what if we change the location of our <code>Post</code> model from <code>App\Post</code> to <code>App\Models\Post</code>? In our database, the <code>commentable_type</code> fields won&apos;t be updated, and thus Laravel cannot find the comments associated with our post since the <code>App\Post</code> model doesn&apos;t exist anymore.</p><p>Yes, we could add a migration to update the values of <code>commentable_type</code> with the correct post model, but that&apos;s quite a bit of work that can be quickly forgotten.</p><p>Laravel has an excellent solution to this: morph maps! Within your application, you register a mapping between the class of your model and a simple identifier. In our scenario, the morph map would look like this:</p><pre><code class="language-php">Relation::morphMap([
    &apos;post&apos; =&gt; &apos;App\Post&apos;,
]);</code></pre><p><br>In the database, we will now use post in the <code>commentable_type</code> row instead of <code>App\Post</code>. Whenever we want to move our Post model, the only thing we have to do is updating the morph map, neat!</p><blockquote>Maintaining morph maps requires discipline</blockquote><p>I cannot count the number of times I forgot to add an entry to a morph map, and the full class name of a model would be stored in the database.</p><p>We&apos;re working on a massive project these days with lots of models. And I know I&apos;m going to forget to add some models to the morph map. My colleagues will forget to add some of these models. I think everyone sometimes will forget to add a model to the map.</p><p>Can&apos;t we do better? Is it possible to automatically generate a morph map, so we don&apos;t forget to add these models?</p><h2 id="dynamically-generating-morph-maps">Dynamically generating morph maps</h2><p>Okay, all we have to do is add a method to a model, so we know its morph identifier. Luckily, each Eloquent model already has a method <code>getMorphClass</code>, which Laravel uses to save the model&apos;s morph identifier.</p><p><code>getMorphClass</code> will return the model&apos;s full class name when the model is not present in the morph map. When the model exists in the morph map, its identifier from the map will be returned. We overwrite this function in each model like this:</p><pre><code class="language-php">class Post extends Model
{
    public function getMorphClass()
    {
        return &apos;post&apos;;
    }
}</code></pre><p></p><p>Cool! We&apos;re halfway done. When we save a comment linked to a post, it will use <code>post</code> as <code>commentable_type</code> and not the full class name.</p><p>But a morph map is still required when we want to load the post associated with the comment since Laravel doesn&apos;t know that <code>post</code> is an <code>App\Models\Post</code> model.</p><p>Creating such a map is now actually really simple. We can search for all the models in our application, run the <code>getMorphClass</code> method on them. And we&apos;re done. We&apos;ve got ourselves a dynamically generated morph map!</p><h2 id="making-sure-every-model-has-a-morph-identifier">Making sure every model has a morph identifier</h2><p>We can even take it a step further. It is still possible that someone forgets to add an implementation of <code>getMorphClass</code> to a model. Let&apos;s fix that!</p><p>We&apos;ll make an abstract base model from which all our models will extend:</p><pre><code class="language-php">abstract class Model extends BaseModel
{
    public function getMorphClass()
    {
        $class = get_class($this);

        throw new Exception(&quot;Model `{$class}` hasn&apos;t implemented `getMorphClass` yet&quot;);
    }
}</code></pre><p></p><p>Now when a model doesn&apos;t implement <code>getMorphClass</code>, an exception is thrown. And we know for sure each model has a morph identifier.</p><p>This won&apos;t create a lot of exceptions in production. Since at the boot process of our application, <code>getMorphClass</code> will be called for each model. In our local setup, we&apos;ll know which models are missing a <code>getMorphClass</code> implementation immediately.</p><p>Cool, we&apos;re done now we&apos;ve got ourselves a dynamically generated morph map!</p><h2 id="package-it-up-">Package it up!</h2><p>I&apos;ve added this functionality into a new package called: <a href="https://github.com/spatie/laravel-morph-map-generator?ref=rubenvanassche.com">laravel-morph-map-generator</a>, which adds some extra functionality like caching dynamically generated morph maps on production, ignoring models, and more!</p><p>Let me know what you think about it.</p>]]></content:encoded></item><item><title><![CDATA[Typing your frontend from the backend]]></title><description><![CDATA[In the past few months, we've been working on one of the biggest projects ever. This created some challenges to keep our frontend and backend types in sync.]]></description><link>https://rubenvanassche.com/typing-your-frontend-from-the-backend/</link><guid isPermaLink="false">5f23d1aa628c71048580d03e</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Fri, 25 Sep 2020 11:10:30 GMT</pubDate><content:encoded><![CDATA[<p>In the past few months, we&apos;ve been working on one of the biggest projects we&apos;ve ever done. However, we had some experience with projects of this size. We knew this one would not only be heavy on the backend but also the frontend.</p><p>At <a href="https://spatie.be/?ref=rubenvanassche.com">Spatie</a>, we fell in love with <a href="https://inertiajs.com/?ref=rubenvanassche.com">Inertia</a>. The framework makes it so easy to create a backend Laravel application with a React frontend. Passing data between these sides is so straightforward. You can give them to a React component just like you would give them to a regular Blade view.</p><p>What made us fall in love with this setup is that we&apos;re now able to use TypeScript, which made our Javascript code type-checked and our frontend code less error-prone.</p><p>But with this type-checking, a new problem popped up. What about structures that would have to be used on both sides of the application? Take, for example, an enum like this one:</p><figure class="kg-card kg-code-card"><pre><code class="language-php">/**
 * @method static self guitar()
 * @method static self piano()
 * @method static self drums()
 */
class Instrument extends Enum
{
}</code></pre><figcaption>An enum using the <a href="https://github.com/spatie/enum?ref=rubenvanassche.com">spatie/enum</a> package</figcaption></figure><p>This enum lives on the backend because we&apos;re going to need it to save a model, validate a request, or make some decisions further in the process. But what happens if we would need this enum on the frontend?</p><p>This enum&apos;s values can be communicated quite easily, get all the options(guitar, piano, and drums) and put them in an array with a corresponding label. Then we provide this array to the react component via Inertia at the end of our controller:</p><pre><code class="language-php">class MusiciansController
{
    public function show(Musician $musician)
    {
        return Inertia::render(&apos;Musician/Show&apos;, [
            &apos;musician&apos; =&gt; $musician,
            // the array we created
            &apos;instrumentOptions&apos; =&gt; Instrument::options(), 
        ]);
    }
}</code></pre><p></p><p>On the frontend in our React component, we now have the following array with instruments:</p><pre><code class="language-json">[
	{&apos;label&apos;: &apos;guitar&apos;, &apos;value&apos;: &apos;guitar&apos;},
    {&apos;label&apos;: &apos;piano&apos;, &apos;value&apos;: &apos;piano&apos;},
    {&apos;label&apos;: &apos;drums&apos;, &apos;value&apos;: &apos;drums&apos;}
]</code></pre><p></p><p>We want to show a select component where the musician can select its favorite instrument. We can pass this array to such a component, and we&apos;re done!</p><p>That&apos;s true for simple forms, but what about a situation where you want to show a checkbox to mark you are playing bass guitar when choosing guitar as an option?</p><p>That would require a condition like this:</p><pre><code class="language-js">if(instrument === &apos;guitar&apos;){
	// Show checkbox
}</code></pre><p></p><p>But actually, what&apos;s the type of this instrument variable? It&apos;s a string coming from the backend, and our frontend code doesn&apos;t know much more about it. We know this is no ordinary string. It&apos;s an enum of type <code>Instrument</code>, which means the string can only have three values: <code>piano</code>, <code>guitar</code>, and <code>drums</code>.</p><p>Luckily TypeScript can help us here. We can create an <code>Instrument</code> type:</p><pre><code class="language-typescript">export type Instrument = &apos;piano&apos; | &apos;guitar&apos; | &apos;drums&apos;;</code></pre><p></p><p>When we now assign the instrument variable to value <code>violin</code>. The TypeScript type checker would then complain since instrument can only be set to <code>piano</code>, <code>guitar</code>, or <code>drums</code>.</p><p>This kind of type checking is cool! But there&apos;s a big catch. We&apos;re writing out types two times: one time in PHP and one time in TypeScript.</p><p>What will happen if we remove the <code>guitar</code> entry from the enum in PHP? It would still be in the TypeScript definition. Wich can break our code or give us incorrect type errors. Or what if we add a violin to our PHP Enum? This can go bad very quickly.</p><p>In small projects where one or two people are working on, this isn&apos;t such a big problem. You can communicate with your colleagues about these changes if the amount of types is small.</p><p>In this project, we worked with seven people, and these seven people would sometimes not work on the project for weeks. There we&apos;re backend developers who wouldn&apos;t write TypeScript and frontend developers who wouldn&apos;t write PHP. Even worse, we knew the number of types grow dramatically in the timespan of the project.</p><p>There were three options now:</p><ol><li>Type nothing; this is the worst option and was a no go for us.</li><li>Try to keep these definitions in sync, but since this project was so big and we were working on it with many people, this was going to get out of hand.</li><li>Try to keep these definitions in sync automatically.</li></ol><h2 id="introducing-typescript-transformer">Introducing Typescript Transformer</h2><p>We&apos;ve made a package for that third option, it&apos;s called <a href="https://github.com/spatie/laravel-typescript-transformer?ref=rubenvanassche.com">laravel-typescript-transformer</a>, and it will convert all the types from the backend that are needed in the frontend. The package will try to transform these types to TypeScript automatically. Let&apos;s take a look at it!</p><p>All we have to do is adding an <code>@typescript</code> annotation to our <code>Instrument</code> enum:</p><pre><code class="language-php">/**
 * @method static self guitar()
 * @method static self piano()
 * @method static self drums()
 * 
 * @typescript
 */
class Instrument extends Enum
{
}</code></pre><p></p><p>Now when running:</p><pre><code class="language-bash">php artisan typescript-transform</code></pre><p></p><p>The package will create a <code>generated.d.ts</code> file in your Laravel resources directory containing:</p><pre><code class="language-typescript">export type Instrument = &apos;piano&apos; | &apos;guitar&apos; | &apos;drums&apos;;</code></pre><p></p><p>Cool! When we add or remove or add an instrument from the enum, and this type of instrument is being used in our code, then the TypeScript checker will notice this and complain. This checker is running almost always, whenever someone is working on the frontend code. Or when we commit code, then a GitHub Action will also check the code, neat!</p><p>Our initial case was to transform enums and keep them in sync. This was so powerful we didn&apos;t stop there!</p><p>Let&apos;s say you have a<a href="https://en.wikipedia.org/wiki/Data_transfer_object?ref=rubenvanassche.com"> data transfer object</a>(DTO), an object with some public properties you can pass around in your application. In the past, we had to keep a type definition for this DTO in PHP and TypeScript. Wouldn&apos;t it be cool if we could have an automatically generated TypeScript definition for that object?</p><p>All you have to do is add the annotation:</p><figure class="kg-card kg-code-card"><pre><code class="language-php">/** @typescript */
class MusicianData extends DataTransferObject
{
    public string $name;
    
    public int $age;

    public Instrument $instrument;

    public static function create(array $data): self
    {
        return new self([
            &apos;name&apos; =&gt; $data[&apos;name&apos;],
            &apos;age&apos; =&gt; $data[&apos;age&apos;],
            &apos;instrument&apos; =&gt; Instrument::make($data[&apos;age&apos;])
        ]);
    }
}</code></pre><figcaption>A DTO using the <a href="https://github.com/spatie/enum?ref=rubenvanassche.com">spatie/data-transfer-object</a> package</figcaption></figure><p>Now when running the Typescript Transformer command, our generated TypeScript file looks like this:</p><pre><code class="language-typescript">export type Instrument = &apos;piano&apos; | &apos;guitar&apos; | &apos;drums&apos;;

export type MusicianData = {
    name : string;
    age: number;
    instrument: Instrument;
}</code></pre><p></p><p>We use this DTO for two purposes:</p><ul><li>When we would give the Musician model to a form in the frontend, so we can build a form for creating/editing the model</li><li>When that form is filled in, and we receive the data from the frontend, which we will use in the backend</li></ul><p>In our project, we stopped using the default Laravel resources in favor of DTO resources that also could be typed on the frontend. In the package documentation, we&apos;ve added a whole <a href="https://spatie.be/docs/typescript-transformer/v1/usage/general-overview?ref=rubenvanassche.com">section</a> on how you could accomplish this.</p><p>We even use these resources to log activity on models, which is very powerful but something for another blog post.</p><h3 id="how-does-it-work">How does it work?</h3><p>In essence, the process of converting PHP classes to Typescript is relatively easy. Let&apos;s take a look at the steps:</p><p><strong>Searching for PHP classes that need to be converted</strong><br>The package will look in a directory you defined in your config for classes with a <code>@typescript</code> annotation and make a list. It is possible to add classes to that list without <code>@typescript</code> annotation automatically, you can read more about it <a href="https://spatie.be/docs/typescript-transformer/v1/usage/selecting-classes-using-collectors?ref=rubenvanassche.com">here</a>.<br><br><strong>Finding a transformer for the class</strong><br>Transformers are the heart of the package. They will take a PHP class and output a TypeScript representation. We&apos;ve included some essential transformers in the package, but you probably also want to write your <a href="https://spatie.be/docs/typescript-transformer/v1/usage/using-transformers?ref=rubenvanassche.com">own</a> transformers.<br><br><strong>Replacing missing symbols</strong><br>Each transformer will output a TypeScript representation, but what about types that link to other types? For example, in our <code>MusicianData</code>, we link to the <code>Instrument</code> enum. At the time of transforming, we do not know what the TypeScript type of <code>Instrument</code> will be, so we cannot add the type to the <code>MusicianData</code> type (yet).</p><p>That&apos;s why each transformer will keep track of a list of links to other types it does not know yet. In this step, we replace these missing links with the correct types.</p><p><strong>Write out the generated TypeScript</strong><br>In this last step, we&apos;ll write down the transformed types into one TypeScript file that you defined in your config.</p><p>That&apos;s it! Although this transformation looks relatively straightforward, there&apos;s a lot more to look at. For example, you can create <a href="https://spatie.be/docs/typescript-transformer/v1/usage/using-transformers?ref=rubenvanassche.com#inline-types">inline types</a>, or use <a href="https://spatie.be/docs/typescript-transformer/v1/usage/annotations?ref=rubenvanassche.com">another name</a> for the TypeScript type or completely <a href="https://spatie.be/docs/typescript-transformer/v1/dtos/changing-types-with-class-property-processors?ref=rubenvanassche.com">modify</a> the types of the properties of DTO&apos;s as you wish.</p><p>Be sure to take a look at the <a href="https://github.com/spatie/laravel-typescript-transformer?ref=rubenvanassche.com">laravel-typescript-transformer</a> package. We&apos;ve also made a <a href="https://github.com/spatie/typescript-transformer?ref=rubenvanassche.com">typescript-transformer</a> package that&apos;s not Laravel specific, which is the basis for the Laravel package and can be used in any PHP project.</p><h2 id="the-future">The future</h2><p>Although the initial version is just released, I&apos;ve already some things on my list for the second version of the package:</p><ul><li>A watcher which automatically transforms types as they are changed</li><li>Support for nullable TypeScript types</li><li>Support for omitting namespaces in TypeScript types</li></ul>]]></content:encoded></item><item><title><![CDATA[Testing Laravel packages with GitHub Actions]]></title><description><![CDATA[Let's take a look at testing Laravel Packages with GitHub Actions]]></description><link>https://rubenvanassche.com/testing-php-packages-with-github-actions/</link><guid isPermaLink="false">5dee5748fdbe6e643b1c55c6</guid><category><![CDATA[GitHub Actions]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Wed, 15 Jan 2020 10:22:56 GMT</pubDate><content:encoded><![CDATA[<p>GitHub Actions are hot! Since my last <a href="https://rubenvanassche.com/getting-started-with-github-actions/">post</a>, we <a href="https://github.com/spatie/laravel-backup/blob/master/.github/workflows/run-tests.yml?ref=rubenvanassche.com">started</a> <a href="https://github.com/spatie/laravel-model-states/blob/v2/.github/workflows/test.yml?ref=rubenvanassche.com">using</a> <a href="https://github.com/spatie/laravel-resource-links/blob/6899148c094d1a0435aa1e11c291430b3922c5dd/.github/workflows/test.yml?ref=rubenvanassche.com">it</a> <a href="https://github.com/spatie/image-optimizer/blob/github-actions/.github/workflows/tests.yml?ref=rubenvanassche.com">everywhere</a> we can! Today we&apos;re going to look at how you can use GitHub Actions to test Laravel Packages. You might think this would be the same as using Actions on a default Laravel project, but some changes should be made to make it work.</p><p><em>Disclaimer: If you haven&apos;t read my previous </em><a href="https://rubenvanassche.com/getting-started-with-github-actions/">post</a><em>, please, read it first! It explains the basic principles of Actions which we continue to use in this post.</em></p><h2 id="welcome-to-the-matrix-">Welcome to the matrix &#x1F534;-&#x1F535;</h2><p>Let&apos;s get started and continue where we left the previous time: creating a testing workflow. First, we create a new workflow file in our repository: &#xA0;<code>.github/workflows/testing.yml</code> and start with a basic template:</p><pre><code class="language-YAML">name: Test

on: [push]

jobs:
    test:
        runs-on: ubuntu-latest</code></pre><p>Time for the first difference between application and package testing, let&apos;s add a <code>strategy</code> key to the <code>test</code> section:</p><pre><code class="language-YAML">test:
    runs-on: ubuntu-latest
    strategy:
        fail-fast: true
        matrix:
            php: [7.2, 7.3, 7.4]
            laravel: [5.8.*, 6.*]
            dependency-version: [prefer-lowest, prefer-stable]</code></pre><p>Wow! There&apos;s quite a lot happening here. Let&apos;s go through it step-by-step. What we&apos;re trying to achieve is matrix testing, simply said: we try to run our tests on a lot of different configurations so we can be sure that our package is working in different environments.</p><p>Each configuration will spawn a GitHub Action job with specific configuration options. GitHub Actions will inject the configuration options for each configuration as variables so we can use them through the workflow. In this case, the configuration options are: the PHP version, the Laravel version, and if we desire our composer dependencies to be stable or the lowest possible.</p><p>An example configuration could look like this:</p><ul><li>PHP 7.4</li><li>Laravel 5.8.*</li><li>prefer-stable dependencies</li></ul><p>In our workflow under the <code>strategy</code> section we create a <code>matrix</code> section, here we can specify the configuration options by giving them a key and an array of options from which GitHub Actions will create configurations.</p><p>When this workflow runs, it will spawn a total of <strong>12</strong> jobs! You can calculate this as such:</p><p><em> 3 PHP versions (7.2, 7.3, 7.4) * 2 Laravel versions (5.8.*, 6.*) and 2 composer flags (prefer-lowest, prefer-stable) = 12. </em></p><p>Next to the <code>matrix</code> key in the <code>strategy</code> section we&apos;ve added that <code>fail-fast</code> will be true. This setting is not required but becomes quite handy when you&apos;re spawning a lot of jobs. If one of your configurations fails, then all the other configurations of that workflow will immediately fail. This will reduce the number of jobs running without purpose because they will also probably fail.</p><p>When testing a Laravel package, we also going to need <a href="https://github.com/orchestral/testbench?ref=rubenvanassche.com">Testbench</a>. The problem is Laravel 5.8.* requires Testbench 3.8.* and Laravel 6.* requires Testbench 4.*. We can add an <code>include</code> section to our <code>matrix</code> in which we simply say: &quot;if this variable is this value, then add another variable with this value.&quot; In our workflow, it looks like this:</p><pre><code class="language-YAML">matrix:
    php: [7.2, 7.3, 7.4]
    laravel: [5.8.*, 6.*]
    dependency-version: [prefer-lowest, prefer-stable]
    include:
        -   laravel: 6.*
            testbench: 4.*
        -   laravel: 5.8.*
            testbench: 3.8.*</code></pre><h2 id="using-matrix-variables">Using matrix variables</h2><p>So we defined our configurations, now in every job, we will have access to the following variables: <code>php</code>, <code>laravel</code>, <code>dependency-version</code>, and <code>testbench</code>. Let&apos;s try this out! We&apos;re going to give our job a name with the variables of our configuration. This can be done by adding a new key <code>name</code> in the <code>test</code> section:</p><pre><code class="language-YAML">test:
    runs-on: ubuntu-latest
    name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }}
    strategy:
        ...
</code></pre><p>As you can see the configuration variables are scoped by the <code>matrix</code> keyword, with our example configuration from above the name of the job will look like: <em>PHP 7.4 - Laravel 5.8.*</em></p><h2 id="stepping-through-the-matrix">Stepping through the matrix</h2><p>Everything is set up let&apos;s start testing! First we will be defining the steps that will run in each job. We start with checking out the code from GitHub:</p><pre><code class="language-YAML">-   name: Checkout code
    uses: actions/checkout@v1</code></pre><p>Then we&apos;ll install the PHP version of our configuration:</p><pre><code class="language-YAML">-   name: Setup PHP
    uses: shivammathur/setup-php@v1
    with:
        php-version: ${{ matrix.php }}</code></pre><p>It is time for the next difference between application and package testing with Actions! In the application workflow, we used the build-in PHP version of Actions, which at the time of writing is 7.3. If we want to matrix test our PHP version, then we have to set it up. This can be done by using the <a href="https://github.com/shivammathur/setup-php?ref=rubenvanassche.com">setup-php</a> action. It takes the php-version as an option which we fill in with our configuration variable <code>${{ matrix.php }}</code>.</p><p>Want to add some extra extensions to PHP or change some ini values? Then check the <a href="https://github.com/shivammathur/setup-php?ref=rubenvanassche.com">GitHub page</a> of the setup-php action for more information.</p><p>Next, we install the composer dependencies:</p><pre><code class="language-YAML">-   name: Install dependencies
    run: |
        composer require &quot;laravel/framework:${{ matrix.laravel }}&quot; &quot;orchestra/testbench:${{ matrix.testbench }}&quot; --no-interaction --no-update
        composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest</code></pre><p>We first require a specific Laravel and Testbench version before installing our other dependencies with the <em>prefer-stable</em> or <em>prefer-lowest</em> composer flag. </p><p>Of course, we can also cache our dependencies, so the next time the Action runs it will even run faster. In the application workflow, we had access to a <em>composer.lock</em> file that we used as a key to identify if the cache entry existed. But we don&apos;t commit a <em>composer.lock </em>file to the repositories of packages.</p><p>The solution is to use the hash of the <em>composer.json</em> file and the configuration variables. The step will in the end look like this:</p><pre><code class="language-YAML">-   name: Cache dependencies
    uses: actions/cache@v1
    with:
        path: ~/.composer/cache/files
        key: dependencies-${{ matrix.dependency-version }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles(&apos;composer.json&apos;) }}
</code></pre><p>Do not forget to put this step before the <em>Install dependencies</em> step. Otherwise your cache would not be used.</p><p>What if one of the packages in <em>composer.json</em> get&apos;s bumped with a new version? We would expect that our cache will be invalidated, but that&apos;s not going to happen &#x1F61E;. The only times our cache will be invalidated will be when our<em> composer.json</em> file changes. Now, this is not that big of a problem. Every week GitHub purges the caches and Composer will always use the latest dependencies possible even if they are not cached.</p><p>Now for the last step of the workflow, we run our tests:</p><pre><code class="language-YAML">-   name: Execute tests
    run: vendor/bin/phpunit</code></pre><p>If everything is going well, your workflow should look like this:</p><figure class="kg-card kg-image-card kg-width-full"><img src="https://rubenvanassche.com/content/images/2019/12/Schermafbeelding-2019-12-23-om-16.36.35.png" class="kg-image" alt loading="lazy"></figure><h2 id="badges">Badges</h2><p>Who doesn&apos;t like badges? You can create them for your GitHub Actions. Head over to <a href="https://shields.io/?ref=rubenvanassche.com">shields.io</a> and search for: <em>GitHub Workflow Status. </em></p><figure class="kg-card kg-image-card"><img src="https://rubenvanassche.com/content/images/2019/12/Schermafbeelding-2019-12-23-om-16.27.11.png" class="kg-image" alt loading="lazy"></figure><h2 id="conclusion">Conclusion</h2><p>This powerful yet straightforward workflow works for 90% of our packages. Matrix testing provides us the trust that our packages run on the environments we defined. You can find the workflow we&apos;ve build <a href="https://github.com/spatie/laravel-resource-links/blob/master/.github/workflows/test.yml?ref=rubenvanassche.com">here</a>.</p><p>If you want to read more about testing packages, then head over to <a href="https://freek.dev/1546-using-github-actions-to-run-the-tests-of-laravel-projects-and-packages?ref=rubenvanassche.com">Freek</a>&apos;s blog, where he adds Github Actions to <a href="https://github.com/facade/ignition/actions?ref=rubenvanassche.com">Laravel Ignition</a>. In the next part, we will take a look at creating our own action for the checking and fixing of our PHP styling. See you then!</p>]]></content:encoded></item><item><title><![CDATA[Getting started with GitHub Actions and Laravel]]></title><description><![CDATA[When GitHub released its new product: GitHub Actions a whole new world opened for developers. Let's dive right in and see what it brings for the Laravel community. ]]></description><link>https://rubenvanassche.com/getting-started-with-github-actions/</link><guid isPermaLink="false">5dea41e0fdbe6e643b1c555c</guid><category><![CDATA[GitHub Actions]]></category><dc:creator><![CDATA[Ruben Van Assche]]></dc:creator><pubDate>Mon, 09 Dec 2019 12:30:24 GMT</pubDate><content:encoded><![CDATA[<p>At <a href="https://rubenvanassche.com/p/c69ae1ef-5bf5-4497-9626-cfadc6cb55aa/spatie.be">Spatie</a>, we have been using Circle CI, Travis CI, Chipper CI, and other services for quite a while, but we couldn&apos;t find an exact fit for our cases. We were excited when GitHub announced its CI/CD service named GitHub Actions this year and think this might be the CI/CD service for all our projects.</p><p>With GitHub Actions, you can spin up a container in no time, use actions from other authors to run in your container, commit to your repositories, and that all without leaving GitHub. And even better, the free tier GitHub provides you is very generous. With a free account, you can spin up to 20 containers concurrently and run 2000 minutes of containers for free.</p><p>Using GitHub actions starts with workflows you define in your repository, which consist of jobs that will be run concurrently, and in these jobs, you can define a sequence of steps that should be executed. A workflow will be triggered by an event like a push, pull request, or even a <a href="https://help.github.com/en/actions/automating-your-workflow-with-github-actions/events-that-trigger-workflows?ref=rubenvanassche.com#scheduled-events-schedule">cron</a> you can define.</p><p>You can write workflows in YAML, which makes them easy to write and read. In the beta version of GitHub Actions you had to use Ocaml, which was quite hard to comprehend, and there was almost no documentation. If you were a bit frightened by the beta version, like me, then rest assured: the YAML version is easier to use, and the documentation is well written.</p><h2 id="creating-a-test-workflow"><strong><strong>Creating a test workflow</strong></strong></h2><!--kg-card-begin: markdown--><p>Let&apos;s say we want to run our test suite each time someone commits to our repository. First, we create a <code>tests.yml</code> workflow file in <code>.github/workflows</code>:</p>
<!--kg-card-end: markdown--><pre><code class="language-yml">name: Tests (PHP)

on: [push]

jobs:
    tests:
        name: Run tests
        runs-on: ubuntu-latest
        steps:
            -   uses: actions/checkout@v1</code></pre><!--kg-card-begin: markdown--><p>This is the most basic workflow we can have. First, we define by the <code>on</code> key that this workflow will run when a commit was pushed. Then a job named <code>Run tests</code> is created which will run on the latest version of Ubuntu, it is possible to use other machines like Windows or macOS.</p>
<p>The last thing we do is defining the steps this job consists of. In this example, this is just one step: we checkout the code of the commit that was pushed. A step can be an action that was defined by you or someone else, and that will run some code. You can also immediately run code in the shell of the container.</p>
<p>In this case, the step is an action created by GitHub itself, we import it with the <code>uses</code> keyword. Actions can be imported from folders within your project or from GitHub, just like the checkout action.</p>
<p>Let&apos;s add some steps and go through them step-by-step! A small note, within the examples below I&apos;ve changed the indentation a bit for readability, don&apos;t forget to indent it back correctly when creating your workflow!</p>
<!--kg-card-end: markdown--><p><strong><strong>Running composer</strong>&#x200C;</strong></p><pre><code class="language-yml">-   name: Run composer install
    run: composer install -n --prefer-dist
    env:
        APP_ENV: testing</code></pre><!--kg-card-begin: markdown--><p>Downloading and installing dependencies via composer will be the first thing we do. The <code>uses</code> keyword is now changed to <code>run</code> so we can execute commands directly in the container. There&apos;s also an <code>env</code> key, here you can pass in variables like in your Laravel <code>.env</code> file, neat!</p>
<!--kg-card-end: markdown--><h3 id="preparing-laravel-"><strong><strong>Preparing Laravel</strong>&#x200C;</strong></h3><pre><code class="language-yml">-   name: Prepare Laravel Application
    run: |
        cp .env.example .env
        php artisan key:generate</code></pre><!--kg-card-begin: markdown--><p>Next we create our Laravel application by making an  <code>.env</code> file from the <code>.env.example</code> and by setting an application key. The pipe on the first line of the <code>run</code> keyword makes it possible to execute multiple lines of commands.</p>
<!--kg-card-end: markdown--><h3 id="running-yarn-"><strong><strong>Running Yarn</strong>&#x200C;</strong></h3><pre><code class="language-yml">-   name: Cache yarn dependencies
    uses: actions/cache@v1
    with:
        path: node_modules
        key: yarn-${{ hashFiles(&apos;yarn.lock&apos;) }}

-   name: Run yarn
    run: yarn &amp;&amp; yarn dev</code></pre><!--kg-card-begin: markdown--><p>Two steps? We are running yarn in the second step, a simple action by now that will create a <code>node_modules</code> directory with the dependencies of the project. The first step will cache the <code>node_modules</code> directory. This is specified by <code>path</code> within the <code>with</code> key of our step. We will cache the <code>node_modules</code> directory as long as the hash of the <code>yarn.lock</code> file stays the same, we can specify this with <code>key</code>.</p>
<p>The <code>with</code> key of an action allows you to configure how actions run, you can read about these parameters in the readme&apos;s of actions. The <code>${{ }}</code> syntax allows you to run code within your YAML file. You can read more about it <a href="https://help.github.com/en/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions?ref=rubenvanassche.com#about-context-and-expressions">here</a>.</p>
<p>We can also cache the composer dependencies installing, instead of caching the <code>node_modules</code> directory we cache the <code>vendor</code> directory. We can write this action as such:</p>
<!--kg-card-end: markdown--><pre><code class="language-yml">-   name: Cache composer dependencies
    uses: actions/cache@v1
    with:
        path: vendor
        key: composer-${{ hashFiles(&apos;composer.lock&apos;) }}

-   name: Run composer install
    run: composer install -n --prefer-dist
    env:
        APP_ENV: testing</code></pre><h3 id="testing-our-application"><strong><strong>Testing our application</strong></strong></h3><!--kg-card-begin: markdown--><p>We&apos;ve prepared our container for running tests, and now the time has come to do that, let&apos;s have a look at the step:</p>
<!--kg-card-end: markdown--><pre><code class="language-yml">-   name: Run tests
    run: ./vendor/bin/phpunit
    env:
        APP_ENV: testing</code></pre><!--kg-card-begin: markdown--><p>Nothing fancy here: we run PHPUnit from the vendor dir and set the environment to testing. Our test suite will use the env variables set by the <code>phpunit.xml</code> file:</p>
<!--kg-card-end: markdown--><pre><code class="language-xml">        &lt;env name=&quot;APP_ENV&quot; value=&quot;testing&quot;/&gt;
        &lt;env name=&quot;CACHE_DRIVER&quot; value=&quot;array&quot;/&gt;
        &lt;env name=&quot;SESSION_DRIVER&quot; value=&quot;file&quot;/&gt;
        &lt;env name=&quot;QUEUE_CONNECTION&quot; value=&quot;sync&quot;/&gt;
        &lt;env name=&quot;DB_CONNECTION&quot; value=&quot;sqlite&quot;/&gt;
        &lt;env name=&quot;DB_NAME&quot; value=&quot;:memory:&quot;/&gt;
        &lt;env name=&quot;DEBUGBAR_ENABLED&quot; value=&quot;false&quot;/&gt;</code></pre><!--kg-card-begin: markdown--><p>But you&apos;re free to change these variables in your action, for example, you could change the type of database connection to MySQL as such:&#x200C;</p>
<!--kg-card-end: markdown--><pre><code class="language-yml">-   name: Run tests
    run: ./vendor/bin/phpunit
    env:
        APP_ENV: testing
        DB_CONNECTION: mysql
        DB_NAME: our-awesome-tested-app
        DB_PASSWORD: 
        DB_USER: root</code></pre><!--kg-card-begin: markdown--><p>The last thing we do is storing any logs created by our Laravel application when the tests crash, this can be achieved by using the upload artifacts action:&#x200C;</p>
<!--kg-card-end: markdown--><pre><code class="language-yml">-   name: Upload artifacts
    uses: actions/upload-artifact@master
    if: failure()
    with:
        name: Logs
        path: ./storage/logs</code></pre><!--kg-card-begin: markdown--><p>As you can see, we&apos;ve added a new keyword to our step: <code>if</code>. This will make this action only run if a certain condition is true. In this case, this action will only run if the previous action failed due to the <code>failure()</code> expression, there are a lot of other expressions that you can use, for example, the <code>always()</code> expression will always run your step even if the previous steps failed.</p>
<!--kg-card-end: markdown--><h2 id="running-the-workflow">Running the workflow</h2><p>Now it&apos;s time for the big moment! Commit and push this workflow and head over to your repository in GitHub, in the `Actions` tab, &#xA0;you will see a container getting started, and it will go through all our steps!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://rubenvanassche.com/content/images/2019/12/EK81in5X0AABXhz.jpeg" class="kg-image" alt loading="lazy"><figcaption>Success &#x1F929;</figcaption></figure><h2 id="creating-a-mysql-database-in-the-job"><strong><strong>Creating a mysql database in the </strong>job</strong></h2><!--kg-card-begin: markdown--><p>In the example above, we used an SQLite database for testing, this is extremely fast but has some disadvantages like no support for JSON columns. You can also use a MySQL database, but it requires a bit more setup.</p>
<p>We&apos;re going to pull in a MySQL docker image, this will start a MySQL server in our container. We can do this by adding a <code>services</code> key to the <code>tests</code> entry like so:</p>
<!--kg-card-end: markdown--><pre><code class="language-yml">// ...

jobs:
    tests:
        // ...

        services:
            mysql:
                image: mysql:5.7
                env:
                    MYSQL_ALLOW_EMPTY_PASSWORD: yes
                    MYSQL_DATABASE: our-awesome-tested-app
                ports:
                    - 3306
                options: --health-cmd=&quot;mysqladmin ping&quot; --health-interval=10s --health-timeout=5s --health-retries=3
                
        // ...
</code></pre><!--kg-card-begin: markdown--><p>Within the <code>services</code> section of the workflow, we can define services that will run in the background during the execution of the workflow. You could, for example, also add a Redis server here.</p>
<p>We add a new MySQL service to the <code>services</code> section. For this service we will be using the <code>mysql:5.7</code> image from docker. This is an <a href="https://hub.docker.com/_/mysql?ref=rubenvanassche.com">official</a> MySQL docker image that can be configured. We configure the creation of a new database called <code>our-awesome-tested-app</code> and allow the root password to be empty in the <code>env</code> section, the port for the MySQL database will be 3306 by setting it in the <code>ports</code> key.</p>
<p>The last section in our MySQL service is the <code>options</code> key. Here we can define some specific options to the docker service. In this case, we will wait until the MySQL server responds so we know for sure it is running.</p>
<!--kg-card-end: markdown--><h2 id="conclusion">Conclusion</h2><p>GitHub Actions are a welcome addition to every developer&apos;s toolbelt, I will be using it for all of my next projects. Within the next few weeks I will try to publish some more posts about creating your own actions, using a matrix to test multiple PHP versions and a workflow to lint and fix your PHP/Js code. See you then!</p><p><em>Want the whole workflow file in one piece? You can find it <a href="https://gist.github.com/rubenvanassche/4fa2a9ab58454e77ba8a457941ffc0c5?ref=rubenvanassche.com">here</a>.</em></p>]]></content:encoded></item></channel></rss>