<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ash Allen</title>
    <description>The latest articles on Forem by Ash Allen (@ashallendesign).</description>
    <link>https://forem.com/ashallendesign</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F483102%2F6d940290-12d0-4c4a-8be9-1a9fc955d203.jpeg</url>
      <title>Forem: Ash Allen</title>
      <link>https://forem.com/ashallendesign</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ashallendesign"/>
    <language>en</language>
    <item>
      <title>Measuring Performance with the "Benchmark" Class in Laravel</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Fri, 23 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/ashallendesign/measuring-performance-with-the-benchmark-class-in-laravel-5eh7</link>
      <guid>https://forem.com/ashallendesign/measuring-performance-with-the-benchmark-class-in-laravel-5eh7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When building Laravel applications, I often need to measure the execution time of different code blocks. This could be when I'm experimenting with different approaches to solving a problem or optimising existing code.&lt;/p&gt;

&lt;p&gt;Although there are dedicated profiling and performance-monitoring tools available, I sometimes just need a rough idea of performance rather than a detailed analysis. This is where the &lt;code&gt;Illuminate\Support\Benchmark&lt;/code&gt; class comes in handy.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Benchmark&lt;/code&gt; class provides multiple simple ways to measure the execution time of a piece of code.&lt;/p&gt;

&lt;p&gt;In this article, we'll take a look at how to use the &lt;code&gt;Benchmark&lt;/code&gt; class in Laravel to measure the performance of different code blocks, along with some important considerations to keep in mind when using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the &lt;code&gt;Benchmark&lt;/code&gt; class
&lt;/h2&gt;

&lt;p&gt;As I mentioned, I use the &lt;code&gt;Benchmark&lt;/code&gt; class quite often when building new features and experimenting with different approaches to solving a problem. Sometimes I have an idea of how I'm going to implement a feature, but I'm not sure it will be efficient enough. So in this scenario, I might try out a couple of different implementations and use the &lt;code&gt;Benchmark&lt;/code&gt; class to measure their execution times. This then allows me to make a more informed decision about which approach to use based on real numbers rather than just a hunch.&lt;/p&gt;

&lt;p&gt;Sometimes, the results show that the differences between implementations are negligible and unlikely to have a significant impact on overall performance. Other times, the results can be quite surprising, revealing that one approach is significantly faster than another. In these cases, using the &lt;code&gt;Benchmark&lt;/code&gt; class can be incredibly useful for identifying potential bottlenecks before they become a problem in production.&lt;/p&gt;

&lt;p&gt;You can interact with the &lt;code&gt;Benchmark&lt;/code&gt; class in three different ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Benchmark::measure()&lt;/code&gt; - Measures the execution time and returns it as a float value (in milliseconds).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Benchmark::value()&lt;/code&gt; - Measures the execution time and returns both the result of the code and the execution time as a float value (in milliseconds).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Benchmark::dd()&lt;/code&gt; - Measures the execution time and dumps it using &lt;code&gt;dd()&lt;/code&gt; as a formatted string (in milliseconds).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's take a look at each of these methods. We'll stick to simple examples (inspired by those in the &lt;a href="https://laravel.com/docs/12.x/helpers#benchmarking" rel="noopener noreferrer"&gt;Laravel documentation&lt;/a&gt;) that work really well for demonstrating the &lt;code&gt;Illuminate\Support\Benchmark&lt;/code&gt; class.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using "Benchmark::measure()"
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Benchmark::measure()&lt;/code&gt; method accepts a &lt;a href="https://ashallendesign.co.uk/blog/a-beginner-s-guide-to-closures-and-arrow-functions-in-php" rel="noopener noreferrer"&gt;closure&lt;/a&gt; (or an array of closures, which we'll get to later) and returns the execution time as a float value (in milliseconds).&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Benchmark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$executionTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Benchmark&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// $executionTime will be: 0.271625&lt;/span&gt;

&lt;span class="c1"&gt;// This means the code took approximately 0.27 milliseconds to execute.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also accepts a second argument, &lt;code&gt;iterations&lt;/code&gt;, which specifies how many times to run the benchmarked code. The returned value will be the average execution time across all iterations, which is really useful for getting a more accurate measurement, since individual runs can be affected by factors like CPU load and memory usage.&lt;/p&gt;

&lt;p&gt;You can specify the number of iterations like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Benchmark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$executionTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Benchmark&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;benchmarkables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// $executionTime will be: 0.271625&lt;/span&gt;

&lt;span class="c1"&gt;// This means the code took approximately 0.27 milliseconds to execute on average.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also pass an array of closures to benchmark multiple pieces of code in one go. This is really handy for comparing the performance of different implementations side by side. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Benchmark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$executionTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Benchmark&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;benchmarkables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// $executionTime will be: [0.5, 20.0]&lt;/span&gt;

&lt;span class="c1"&gt;// This means the first closure took approximately 0.5 milliseconds to execute,&lt;/span&gt;
&lt;span class="c1"&gt;// and the second closure took approximately 20.0 milliseconds to execute.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using "Benchmark::value()"
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Benchmark::value()&lt;/code&gt; method is similar to &lt;code&gt;Benchmark::measure()&lt;/code&gt;, but it also returns the benchmarked code's result along with the execution time.&lt;/p&gt;

&lt;p&gt;It accepts a closure and returns an array containing two elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The return value of the closure.&lt;/li&gt;
&lt;li&gt;The execution time as a float value (in milliseconds).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Benchmark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$executionTime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Benchmark&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// $user will be the App\Models\User model with ID 1.&lt;/span&gt;
&lt;span class="c1"&gt;// $executionTime will be: 0.271625&lt;/span&gt;
&lt;span class="c1"&gt;// This means the code took approximately 0.27 milliseconds to execute.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike the &lt;code&gt;Benchmark::measure()&lt;/code&gt; and &lt;code&gt;Benchmark::dd()&lt;/code&gt; methods, &lt;code&gt;Benchmark::value()&lt;/code&gt; does not support passing an array of closures or running multiple iterations. It's designed to measure a single piece of code and return its result along with the execution time. So this is a great option when you want to measure the performance of a specific operation and also use its result in your code without disrupting the flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using "Benchmark::dd()"
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Benchmark::dd()&lt;/code&gt; method is a quick and easy way to measure the execution time of a piece of code and dump the result using Laravel's &lt;code&gt;dd()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;It works in pretty much the same way as the &lt;code&gt;Benchmark::measure()&lt;/code&gt; method, but instead of returning the execution time, it dumps it directly to the screen. &lt;/p&gt;

&lt;p&gt;It's important to note that this method returns the execution time as a formatted string (in milliseconds) rather than a raw float value; for example, &lt;code&gt;"0.270ms"&lt;/code&gt; rather than &lt;code&gt;0.270625&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's look at an example of how to use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Benchmark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Benchmark&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// The following will be dumped: "0.270ms"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to the &lt;code&gt;Benchmark::measure()&lt;/code&gt; method, you can also specify the number of iterations to run the benchmarked code, and the average execution time will be returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Benchmark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Benchmark&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;benchmarkables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The following will be dumped: "0.270ms"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also pass an array of closures to benchmark multiple pieces of code in one go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Benchmark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Benchmark&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;benchmarkables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The following will be dumped: ["0.5ms", "20.0ms"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  No External Tools Required
&lt;/h2&gt;

&lt;p&gt;One of the benefits of using the &lt;code&gt;Illuminate\Support\Benchmark&lt;/code&gt; class is that you don't need to set up any external tools or services to get started. You can use it directly within your Laravel application, making it easy to integrate into your existing workflow.&lt;/p&gt;

&lt;p&gt;As a &lt;a href="https://ashallendesign.co.uk/services/laravel-web-development" rel="noopener noreferrer"&gt;freelance Laravel developer&lt;/a&gt;, I'm often brought on board to work on existing projects. Sometimes, the client might not want data being sent to an external service for privacy or security reasons. Other times, they might not have the budget to pay for a dedicated profiling tool. Similarly, it might be difficult to get the application working with an external tool due to infrastructure or networking constraints, or because of the need to install/update Composer packages or PHP extensions. Or maybe you just want to quickly measure a piece of code's performance without the hassle of setting up an external tool.&lt;/p&gt;

&lt;p&gt;So in these cases, the &lt;code&gt;Benchmark&lt;/code&gt; class is a great alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things To Keep in Mind
&lt;/h2&gt;

&lt;p&gt;Although the &lt;code&gt;Benchmark&lt;/code&gt; class is a handy tool for measuring performance, there are a few important considerations to keep in mind when using it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not a Replacement for Dedicated Profiling Tools
&lt;/h3&gt;

&lt;p&gt;It's important to remember that the &lt;code&gt;Benchmark&lt;/code&gt; class only measures execution time and should be used as a guide rather than an absolute. It doesn't provide other important performance metrics, such as memory usage, database query counts, and so on. For these reasons, it's not a like-for-like replacement for proper profiling and performance monitoring tools such as &lt;a href="https://ashallendesign.co.uk/blog/identifying-performance-issues-in-laravel-with-inspector" rel="noopener noreferrer"&gt;Inspector&lt;/a&gt;, Laravel Nightwatch, or Blackfire. So if you're looking for a more in-depth performance analysis, I'd recommend using one of those tools instead.&lt;/p&gt;

&lt;p&gt;However, this doesn't mean the &lt;code&gt;Benchmark&lt;/code&gt; class isn't useful. It's still a fantastic tool that can help you quickly experiment with different approaches and optimisations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local vs Production Benchmarking
&lt;/h3&gt;

&lt;p&gt;Another key point to remember is that no matter how closely you match your local environment to production, there will always be differences that can affect performance. Factors such as infrastructure, hardware capabilities, network setup and latency, dataset size, and traffic patterns can all impact performance in ways that are hard to replicate locally. So always treat the results as a guide rather than an absolute when benchmarking locally. Otherwise, you might end up making decisions based on inaccurate data.&lt;/p&gt;

&lt;p&gt;If you don't want to, or aren't able to, use an external performance monitoring tool, the &lt;code&gt;Benchmark&lt;/code&gt; class is still a great way to quickly measure performance in a production environment. For example, you could use it to measure the performance of a new feature or optimisation after deploying it to production, and log the results for later analysis. This way, you can gain better insight into how your changes perform in the real world. But remember that the results won't be as reliable or detailed as a proper profiling tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running Benchmarks Multiple Times
&lt;/h3&gt;

&lt;p&gt;In addition to ensuring benchmarks are run multiple times per run (via the &lt;code&gt;iterations&lt;/code&gt; parameter), I also like to run the entire benchmark multiple times.&lt;/p&gt;

&lt;p&gt;This helps account for any performance variability caused by external factors. For instance, your machine might have been running other unrelated processes in the background while you were running the benchmark, which may have slowed things down.&lt;/p&gt;

&lt;p&gt;As an example, let's say you run the following code locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Benchmark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$executionTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Benchmark&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;measure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;benchmarkables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the benchmarked code will be run 10 times, and the average execution time will be returned. But if your machine was doing something resource-intensive in the background while all 10 of those closures were running, all of the runs may appear slower than they actually are. So I like to wait 30 seconds or so after running the benchmarks, and then run them again. I might do this 2 or 3 times in total.&lt;/p&gt;

&lt;p&gt;Most of the time, the results will be similar across all runs. But in the past, I've had instances where the first run was significantly slower than subsequent runs, particularly on my older machine, which didn't have as much processing power or memory as my current one. If I'd have taken the results from just that first run, I may have made a decision based on inaccurate data.&lt;/p&gt;

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

&lt;p&gt;In this article, we've looked at how to use the &lt;code&gt;Illuminate\Support\Benchmark&lt;/code&gt; class in Laravel to measure the performance of different pieces of code. We've also discussed some important considerations for using it.&lt;/p&gt;

&lt;p&gt;If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "&lt;a href="https://battle-ready-laravel.com" rel="noopener noreferrer"&gt;Battle Ready Laravel&lt;/a&gt;" which covers similar topics in more depth.&lt;/p&gt;

&lt;p&gt;Or, you might want to check out my other 440+ page ebook "&lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;Consuming APIs in Laravel&lt;/a&gt;" which teaches you how to use Laravel to consume APIs from other services.&lt;/p&gt;

&lt;p&gt;If you're interested in getting updated each time I publish a new post, feel free to &lt;a href="https://ashallendesign.co.uk/blog" rel="noopener noreferrer"&gt;sign up for my newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep on building awesome stuff! 🚀&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>webdev</category>
      <category>php</category>
    </item>
    <item>
      <title>Reduce Duplicate Cache Queries in Laravel with "Cache::memo()"</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Thu, 22 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/ashallendesign/reduce-duplicate-cache-queries-in-laravel-with-cachememo-d00</link>
      <guid>https://forem.com/ashallendesign/reduce-duplicate-cache-queries-in-laravel-with-cachememo-d00</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I recently wrote an article about how to use the &lt;code&gt;once&lt;/code&gt; helper function for &lt;a href="https://ashallendesign.co.uk/blog/laravel-memoisation-once-helper" rel="noopener noreferrer"&gt;memoising data in Laravel applications&lt;/a&gt;. At the end of that article, I included a short section on alternative approaches, including the &lt;code&gt;Cache::memo()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Cache::memo()&lt;/code&gt; method is still relatively new (added to Laravel in v12.9.0 via PR &lt;a href="https://github.com/laravel/framework/pull/55304" rel="noopener noreferrer"&gt;#55304&lt;/a&gt; by &lt;a href="https://github.com/timacdonald" rel="noopener noreferrer"&gt;Tim MacDonald&lt;/a&gt; in April 2025), so I don't see it used very often in the wild. But I felt like it deserved its own article to expand on what it does and how it can be useful in your Laravel applications.&lt;/p&gt;

&lt;p&gt;In this &lt;a href="https://ashallendesign.co.uk/blog?category=quickfire" rel="noopener noreferrer"&gt;Quickfire&lt;/a&gt; article, we'll take a look at how to use the &lt;code&gt;Cache::memo()&lt;/code&gt; method to reduce duplicated &lt;a href="https://ashallendesign.co.uk/blog/an-introduction-to-caching-in-laravel" rel="noopener noreferrer"&gt;cache&lt;/a&gt; queries in Laravel applications to improve performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Memoisation?
&lt;/h2&gt;

&lt;p&gt;To understand what the &lt;code&gt;Cache::memo()&lt;/code&gt; method does, we first need to understand what memoisation is.&lt;/p&gt;

&lt;p&gt;Memoisation is a technique in software and web development that involves temporarily storing the results of an expensive function call in memory so that subsequent calls can return the stored result rather than recalculating it. As a result, it can reduce the need to recompute the same value multiple times, or hit external services (like a database, &lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;API&lt;/a&gt;, or cache) repeatedly.&lt;/p&gt;

&lt;p&gt;This means memoisation can sometimes provide significant performance improvements by avoiding duplicated, redundant operations.&lt;/p&gt;

&lt;p&gt;If you want to see some other examples of memoisation in Laravel, you might be interested in checking out my "&lt;a href="https://ashallendesign.co.uk/blog/laravel-memoisation-once-helper" rel="noopener noreferrer"&gt;Memoisation in Laravel Using the "once" Helper&lt;/a&gt;" article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache Memoisation with "Cache::memo()"
&lt;/h2&gt;

&lt;p&gt;Now that we understand the idea behind memoisation, let's take a look at how we can use the &lt;code&gt;Cache::memo()&lt;/code&gt; method in Laravel to reduce duplicated cache queries.&lt;/p&gt;

&lt;p&gt;For all examples in this article, we'll assume our Laravel application uses Redis as its cache store.&lt;/p&gt;

&lt;p&gt;To do this, let's first look at an example of duplicated cache queries that aren't using memoisation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hits the Redis cache&lt;/span&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hits the cache again&lt;/span&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hits the cache yet again&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we can see that we're retrieving the same cache key multiple times. With each call to &lt;code&gt;Cache::get('key')&lt;/code&gt;, we're hitting the Redis cache store again, meaning we're making three separate requests to Redis.&lt;/p&gt;

&lt;p&gt;Of course, in a real-world application, these calls would likely be happening in different parts of your codebase, but the end result is the same: we're making multiple requests to the cache store for the same data.&lt;/p&gt;

&lt;p&gt;Although Redis and other cache stores are typically very fast, making multiple requests for the same data is still inefficient. In a high-traffic application, these duplicate queries can quickly add up and impact performance. So if there are any ways we can reduce these duplicated queries, it's usually a good idea to do so. Remember, lots of small optimisations can add up to a big overall performance improvement.&lt;/p&gt;

&lt;p&gt;So let's look at how we can use the &lt;code&gt;Cache::memo()&lt;/code&gt; method to solve this problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hits the Redis cache&lt;/span&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Returns the memoised value, no cache hit&lt;/span&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Returns the memoised value, no cache hit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we can see that we're using &lt;code&gt;Cache::memo()&lt;/code&gt; and then calling &lt;code&gt;get('key')&lt;/code&gt;. Using &lt;code&gt;Cache::memo()&lt;/code&gt; tells Laravel to memoise the result of the cache query for the duration of the current request. On the first call to retrieve the cache value, Laravel will hit the Redis cache store as normal. It will then store the retrieved value in memory for the duration of the request and return the value. This means that the second and third calls will read the value from memory rather than hitting the Redis cache again.&lt;/p&gt;

&lt;p&gt;This is great because it removes the need for us to hit the Redis cache multiple times for the same data within a single request when we request the value belonging to the &lt;code&gt;key&lt;/code&gt; cache key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Different Cache Stores
&lt;/h3&gt;

&lt;p&gt;If no argument is passed to the &lt;code&gt;Cache::memo()&lt;/code&gt; method, the default cache store defined in your Laravel application's configuration will be used. However, you can also specify a different cache store by passing the store name as an argument to the &lt;code&gt;memo()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;For example, if we wanted to explicitly use the &lt;code&gt;redis&lt;/code&gt; cache store and memoise the values, we could do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'redis'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hits the Redis cache&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'redis'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Returns the memoised value, no cache hit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Modifying Cache Values
&lt;/h3&gt;

&lt;p&gt;It's important to note that if you modify a cache value using &lt;code&gt;Cache::memo()&lt;/code&gt;, the memoised value will be forgotten. This means the next call to retrieve the value will hit the cache store again.&lt;/p&gt;

&lt;p&gt;Take this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Writes to Redis&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hits the Redis cache&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Returns the memoised value, no cache hit&lt;/span&gt;

&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'new value'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Forgets memoised value and writes to Redis&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hits the Redis cache again&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Returns the memoised value, no cache hit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see in the example above, the first &lt;code&gt;Cache::memo()-&amp;gt;get('key')&lt;/code&gt; call hits the Redis cache and memoises the value. The second call returns the memoised value from memory without hitting the cache again.&lt;/p&gt;

&lt;p&gt;However, when we call &lt;code&gt;Cache::memo()-&amp;gt;put('key', 'new value')&lt;/code&gt;, it forgets the memoised value for that key. As a result, the next call to &lt;code&gt;Cache::memo()-&amp;gt;get('key')&lt;/code&gt; hits Redis again to retrieve the updated value, stores it in memory, and then returns it. The subsequent call then returns the newly memoised value.&lt;/p&gt;

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

&lt;p&gt;Hopefully, this &lt;a href="https://ashallendesign.co.uk/blog?category=quickfire" rel="noopener noreferrer"&gt;Quickfire&lt;/a&gt; article has given you a good overview of how to use the &lt;code&gt;Cache::memo()&lt;/code&gt; method in Laravel to reduce duplicate cache queries in your applications. By leveraging memoisation, you can improve the performance of your applications by minimising redundant cache hits.&lt;/p&gt;

&lt;p&gt;If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "&lt;a href="https://battle-ready-laravel.com" rel="noopener noreferrer"&gt;Battle Ready Laravel&lt;/a&gt;" which covers similar topics in more depth.&lt;/p&gt;

&lt;p&gt;Or, you might want to check out my other 440+ page ebook "&lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;Consuming APIs in Laravel&lt;/a&gt;" which teaches you how to use Laravel to consume APIs from other services.&lt;/p&gt;

&lt;p&gt;If you're interested in getting updated each time I publish a new post, feel free to &lt;a href="https://ashallendesign.co.uk/blog" rel="noopener noreferrer"&gt;sign up for my newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep on building awesome stuff! 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>laravel</category>
      <category>php</category>
    </item>
    <item>
      <title>How to Send Telegram Messages in Laravel</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://forem.com/ashallendesign/how-to-send-telegram-messages-in-laravel-lac</link>
      <guid>https://forem.com/ashallendesign/how-to-send-telegram-messages-in-laravel-lac</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When you're building a Laravel application, you might want to send notifications to users via Telegram. This is useful for things like sending updates and alerts. For example, I'm subscribed to Telegram notifications on sites like &lt;a href="https://ohdear.app" rel="noopener noreferrer"&gt;Oh Dear&lt;/a&gt; to notify me of any downtime or issues with my sites. This means if any of my sites or my clients' sites go down, I get an instant notification via Telegram so I can quickly investigate and resolve the issue.&lt;/p&gt;

&lt;p&gt;In this article, we're going to take a quick look at how to send Telegram messages from your Laravel application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Articles
&lt;/h2&gt;

&lt;p&gt;Before we get stuck in, you might also be interested in some of my other notification-related articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ashallendesign.co.uk/blog/send-an-sms-in-laravel-using-vonage-previously-nexmo" rel="noopener noreferrer"&gt;Send an SMS in Laravel using Vonage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ashallendesign.co.uk/blog/send-whatsapp-messages-in-laravel-with-vonage-s-native-sdk" rel="noopener noreferrer"&gt;Send WhatsApp Messages in Laravel with Vonage's Native SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ashallendesign.co.uk/blog/display-desktop-notifications-in-php-cli-applications" rel="noopener noreferrer"&gt;Display Desktop Notifications in PHP CLI Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ashallendesign.co.uk/blog/sending-email-in-laravel-with-mailgun" rel="noopener noreferrer"&gt;Sending Email in Laravel with Mailgun&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up Your Telegram Bot
&lt;/h2&gt;

&lt;p&gt;In order to send messages via Telegram, you'll first need to create a bot in the Telegram app. This bot will be responsible for sending messages to users on your behalf.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://core.telegram.org/bots#how-do-i-create-a-bot" rel="noopener noreferrer"&gt;Telegram documentation&lt;/a&gt; provides a nice guide on creating a bot. The basics are that you need to start a chat with the "BotFather" user in Telegram. This will launch a conversation where you can create and manage your bots.&lt;/p&gt;

&lt;p&gt;Telegram bots are incredibly powerful and are packed with features. So I recommend spending some time exploring the different options available when setting up your bot so it best suits your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;To send the Telegram notifications, we'll use the &lt;code&gt;laravel-notification-channels/telegram&lt;/code&gt; package. The package provides a new notification channel that taps into Laravel's existing notification system, making it super easy to send Telegram messages. At the time of writing this article, the package has 3.1 million installs and 1.1k stars on GitHub.&lt;/p&gt;

&lt;p&gt;You can find the GitHub repo for the package here: &lt;a href="https://github.com/laravel-notification-channels/telegram" rel="noopener noreferrer"&gt;https://github.com/laravel-notification-channels/telegram&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To install the package, you can run the following command in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require laravel-notification-channels/telegram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package should now be installed and ready to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the Chat ID
&lt;/h2&gt;

&lt;p&gt;In order to send a message from your Telegram bot to a user, you first need to know the user's chat ID. A bot cannot initiate a conversation with a user. This means the user must have first interacted with the bot (for example, by sending it a message or clicking the "Start" button within Telegram) to start the chat.&lt;/p&gt;

&lt;p&gt;Once the user has interacted with your bot and started a chat, you can retrieve the chat ID so it can be used to send messages. There are multiple approaches to getting the chat ID. For example, you could use the notification channel package's built-in class to fetch channel updates. Then you can search through the updates to find the chat ID of the user you want to send a message to. The &lt;a href="https://github.com/laravel-notification-channels/telegram?tab=readme-ov-file#retrieving-chat-id" rel="noopener noreferrer"&gt;package's documentation&lt;/a&gt; shows how you can do this.&lt;/p&gt;

&lt;p&gt;Another approach is to configure your Telegram bot to send a webhook to your Laravel application when a conversation starts. From there, you can store the chat ID (sent in the webhook) in your database against the user.&lt;/p&gt;

&lt;p&gt;However, I'm not going to cover this in detail here, as it's beyond the scope of this article, and you'll use a different approach depending on your use case.&lt;/p&gt;

&lt;p&gt;But please remember, whichever approach you use, keep security in mind. You don't want to add any vulnerabilities that allow someone to send messages to arbitrary chat IDs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending Notifications
&lt;/h2&gt;

&lt;p&gt;For Laravel's notification system to know where to send Telegram notifications, we need to define a new &lt;code&gt;routeNotificationForTelegram&lt;/code&gt; method on the notifiable model (usually &lt;code&gt;App\Models\User&lt;/code&gt;). In this case, we'll assume that we're storing the user's Telegram chat ID in a &lt;code&gt;telegram_chat_id&lt;/code&gt; column on the &lt;code&gt;users&lt;/code&gt; table. We can then return this value from the &lt;code&gt;routeNotificationForTelegram&lt;/code&gt; method like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Casts\NotificationPreferences&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Auth\User&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;Authenticatable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Notifications\Notifiable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Authenticatable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Notifiable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;routeNotificationForTelegram&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;telegram_chat_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's create our notification. For the purposes of this article, we'll create a simple notification class that can notify a user when the status of their order in our online store is updated. We'll include a button in the Telegram message that directs the user to our web application to view their order.&lt;/p&gt;

&lt;p&gt;We can create the notification by running the following Artisan command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:notification OrderUpdated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should now have a new notification class at &lt;code&gt;app/Notifications/OrderUpdated.php&lt;/code&gt;. Let's update this class to send a Telegram message, and then we'll discuss what's being done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Notifications&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Bus\Queueable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Notifications\Notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;NotificationChannels\Telegram\TelegramMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderUpdated&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Queueable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Order&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Get the notification's delivery channels.
     *
     * @return array&amp;lt;int, string&amp;gt;
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'mail'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'telegram'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

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

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toTelegram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$notifiable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;TelegramMessage&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;TelegramMessage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello there!'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Your order status has been updated"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'View Order'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'order.show'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code example above, we can see that we're starting by passing in an &lt;code&gt;App\Models\Order&lt;/code&gt; instance to the notification's constructor. We haven't included the code for this model for brevity, but we can assume it holds information about a user's order.&lt;/p&gt;

&lt;p&gt;We're then using the &lt;code&gt;via&lt;/code&gt; method to specify that we want to send the notification via both email and Telegram. In this case, we're only focusing on the Telegram part, so I've left out the email-specific code.&lt;/p&gt;

&lt;p&gt;Next, we have the &lt;code&gt;toTelegram&lt;/code&gt; method. This method builds and returns the actual Telegram message to be sent to the user. We're using the &lt;code&gt;NotificationChannels\Telegram\TelegramMessage&lt;/code&gt; class provided by the notification channel package to create a new message. We're defining that the message should include two lines of text, followed by a button that the user can click to view their order.&lt;/p&gt;

&lt;p&gt;Now that we've defined our notification, we should be able to send it to our user. Let's take this super simple code snippet, which finds a user and an order, then sends the notification to the user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Notifications\OrderUpdated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderUpdated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is configured correctly, the user should receive a Telegram message from your bot notifying them that their order status has been updated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending a Location
&lt;/h3&gt;

&lt;p&gt;The package also allows us to send different types of messages. For example, you can send a location message, which will display a location pin on a map in the Telegram app. You can do this by passing the latitude and longitude to the &lt;code&gt;NotificationChannels\Telegram\TelegramLocation&lt;/code&gt; class like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Notifications&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Notifications\Notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;NotificationChannels\Telegram\TelegramLocation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderUpdated&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toTelegram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$notifiable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;TelegramLocation&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;TelegramLocation&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'51.5072'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0.1276'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sending Images
&lt;/h3&gt;

&lt;p&gt;You can also send images to users via Telegram. You can do this by using the &lt;code&gt;NotificationChannels\Telegram\TelegramFile&lt;/code&gt; class and specifying the file URL or path along with the type of file (in this case, a photo) like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Notifications&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Notifications\Notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;NotificationChannels\Telegram\TelegramFile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderUpdated&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Notification&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toTelegram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$notifiable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;TelegramFile&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;TelegramFile&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Here is an image:'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'https://example.com/image.png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'photo'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Other Messages Types
&lt;/h3&gt;

&lt;p&gt;The package also provides other message types that you can send, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audio&lt;/li&gt;
&lt;li&gt;Document&lt;/li&gt;
&lt;li&gt;Polls&lt;/li&gt;
&lt;li&gt;Stickers&lt;/li&gt;
&lt;li&gt;GIFs&lt;/li&gt;
&lt;li&gt;Venues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think the documentation already does a pretty great job of explaining how to send these different message types, along with examples of what the message will look like in Telegram to the user. So if you're interested in sending any of these other message types, I recommend checking out the &lt;a href="https://github.com/laravel-notification-channels/telegram" rel="noopener noreferrer"&gt;package's documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  On-Demand Notifications
&lt;/h2&gt;

&lt;p&gt;There may also be times when you want to send a notification to a Telegram chat ID that isn't associated with a user in your application. You can do this by sending an on-demand notification using the &lt;code&gt;Illuminate\Support\Facades\Notification::route&lt;/code&gt; method like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Notifications\OrderUpdated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Notification&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'telegram'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'123456789'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderUpdated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we've specified that we should send the &lt;code&gt;App\Notifications\OrderUpdated&lt;/code&gt; notification to the Telegram chat ID &lt;code&gt;123456789&lt;/code&gt;. This allows you to send notifications to arbitrary chat IDs without needing to have a user model associated with them, which can be pretty handy.&lt;/p&gt;

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

&lt;p&gt;Hopefully, this article has given you a quick overview of the basics of sending Telegram messages in Laravel using the &lt;code&gt;laravel-notification-channels/telegram&lt;/code&gt; package. The package makes it super easy to integrate Telegram notifications into your Laravel application by leveraging Laravel's existing notification system.&lt;/p&gt;

&lt;p&gt;If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "&lt;a href="https://battle-ready-laravel.com" rel="noopener noreferrer"&gt;Battle Ready Laravel&lt;/a&gt;" which covers similar topics in more depth.&lt;/p&gt;

&lt;p&gt;Or, you might want to check out my other 440+ page ebook "&lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;Consuming APIs in Laravel&lt;/a&gt;" which teaches you how to use Laravel to consume APIs from other services.&lt;/p&gt;

&lt;p&gt;If you're interested in getting updated each time I publish a new post, feel free to &lt;a href="https://ashallendesign.co.uk/blog" rel="noopener noreferrer"&gt;sign up for my newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep on building awesome stuff! 🚀&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>webdev</category>
      <category>php</category>
    </item>
    <item>
      <title>Memoisation in Laravel Using the `once` Helper</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Tue, 20 Jan 2026 16:19:51 +0000</pubDate>
      <link>https://forem.com/ashallendesign/memoisation-in-laravel-using-the-once-helper-1nk1</link>
      <guid>https://forem.com/ashallendesign/memoisation-in-laravel-using-the-once-helper-1nk1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When building Laravel applications, there may be times when you need to use a value multiple times within the same request lifecycle, but calculating it is expensive or time-consuming. A possible solution optimise performance in these scenarios is "memoisation" (or "memoization").&lt;/p&gt;

&lt;p&gt;Memoisation is the technique of temporarily storing the result of an expensive function call so that subsequent calls to that function return the cached result rather than recalculating it. If you've used &lt;a href="https://ashallendesign.co.uk/blog/an-introduction-to-caching-in-laravel" rel="noopener noreferrer"&gt;caching&lt;/a&gt; before, memoisation is pretty similar, except that it generally lasts only for the duration of a single request, at least in the context of Laravel. Depending on the use case, it can help to reduce calls to databases, &lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;external APIs&lt;/a&gt;, cache stores, or methods that take a long time to run.&lt;/p&gt;

&lt;p&gt;In this article, we're going to take a look at how to use Laravel's built-in &lt;code&gt;once&lt;/code&gt; &lt;a href="https://ashallendesign.co.uk/blog/how-to-create-your-own-helper-functions-in-laravel" rel="noopener noreferrer"&gt;helper function&lt;/a&gt; to implement memoisation in your applications. We'll also explore the differences between using &lt;code&gt;once&lt;/code&gt; inside functions and within object methods, including &lt;a href="https://ashallendesign.co.uk/blog/the-difference-between-self-static-and-parent-in-php" rel="noopener noreferrer"&gt;static methods&lt;/a&gt;. We'll then explore how Laravel Octane integrates with the &lt;code&gt;once&lt;/code&gt; helper to ensure that memoised values are reset between requests. Finally, we'll briefly discuss other approaches to memoisation in Laravel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the "once" Helper in Laravel
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;once&lt;/code&gt; helper function in Laravel is really simple to use. It accepts a closure as an argument; the first time it's called, it returns the closure's result and stores it for future calls within the same request. On subsequent calls, it returns the cached result instead of executing the closure again. Unless the value is cleared, the cached result will persist for the duration of the request. After the request completes, the cached value is discarded. This means a value memoised with &lt;code&gt;once&lt;/code&gt; in a request cannot be accessed in any other requests.&lt;/p&gt;

&lt;p&gt;Let's take a look at a simple example. So we can focus on the memoisation aspect rather than any specific business logic, we'll just imagine our &lt;code&gt;calculateExpensiveValue&lt;/code&gt; function performs an expensive calculation. We'll return the result of &lt;code&gt;random_int&lt;/code&gt; to highlight that the value is only calculated once.&lt;/p&gt;

&lt;p&gt;Imagine we have this function, which isn't using any form of memoisation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Imagine this function performs an expensive calculation. We'll&lt;/span&gt;
    &lt;span class="c1"&gt;// use the "random_int" function to simulate this.&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;random_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// e.g., outputs: 42&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// e.g., outputs: 57&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// e.g., outputs: 13&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, the output changes each time we call the function because it recalculates the value every time.&lt;/p&gt;

&lt;p&gt;Let's take a look at what happens when we wrap the calculation in the &lt;code&gt;once&lt;/code&gt; helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Imagine this function performs an expensive calculation. We'll&lt;/span&gt;
    &lt;span class="c1"&gt;// use the "random_int" function to simulate this.&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;random_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// e.g., outputs: 42&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// outputs: 42&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// outputs: 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, the output remains the same for each call to &lt;code&gt;calculateExpensiveValue&lt;/code&gt; because the result of the first call is cached and returned on subsequent calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using "once" Inside Objects
&lt;/h2&gt;

&lt;p&gt;There are a few "gotchas" to be aware of when using the &lt;code&gt;once&lt;/code&gt; helper inside objects, particularly when it comes to instance methods versus static methods. So I wanted to quickly cover them here so you don't make the same mistakes as I've made in the past.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using "once" in Methods
&lt;/h3&gt;

&lt;p&gt;Let's stick with our previous example, but this time we'll put the &lt;code&gt;calculateExpensiveValue&lt;/code&gt; function inside a class as a method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NumberService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;random_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's call this method a few times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Services\NumberService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$numberService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NumberService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$numberService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// e.g., outputs: 42&lt;/span&gt;
&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$numberService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// outputs: 42&lt;/span&gt;
&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$numberService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// outputs: 42&lt;/span&gt;

&lt;span class="nv"&gt;$anotherNumberService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NumberService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$anotherNumberService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// e.g., outputs: 57&lt;/span&gt;
&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$anotherNumberService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// outputs: 57&lt;/span&gt;
&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$anotherNumberService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// outputs: 57&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, the output is consistent for each instance of the &lt;code&gt;App\Services\NumberService&lt;/code&gt; class, but different between instances. This is because the &lt;code&gt;once&lt;/code&gt; helper caches the result per object instance.&lt;/p&gt;

&lt;p&gt;This has caught me out in the past because I assumed the cached value would be shared across all instances of the class, in a similar way to &lt;a href="https://ashallendesign.co.uk/blog/the-difference-between-self-static-and-parent-in-php" rel="noopener noreferrer"&gt;static properties&lt;/a&gt;. But as we can see, that's not the case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using "once" in Static Methods
&lt;/h3&gt;

&lt;p&gt;However, when the method is static, it is not tied to a specific instance of the class. Instead, it's tied to the class itself, so the cached value is shared across all calls to the static method.&lt;/p&gt;

&lt;p&gt;Let's look at what I mean by this. We'll update our &lt;code&gt;calculateExpensiveValue&lt;/code&gt; method to be static:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NumberService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;random_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we'll call the static method a few times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Services\NumberService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$numberService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NumberService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$numberService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// e.g., outputs: 42&lt;/span&gt;
&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$numberService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// outputs: 42&lt;/span&gt;

&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NumberService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// outputs: 42&lt;/span&gt;
&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;NumberService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// outputs: 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, the output remains consistent across all calls to the static method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using "once" with Laravel Octane
&lt;/h2&gt;

&lt;p&gt;As I mentioned at the beginning of the article, memoised values created with the &lt;code&gt;once&lt;/code&gt; helper are stored in memory and last only for the duration of a single request. This means requests can't access the memoised values of other requests, and when a request completes, the cached values are discarded.&lt;/p&gt;

&lt;p&gt;However, you might be wondering how this works when using Laravel Octane, which keeps your application in memory between requests to improve performance. In this case, if the memoised values were not cleared between requests, they would persist across requests, which is not the intended behaviour.&lt;/p&gt;

&lt;p&gt;Laravel Octane handles this in a really cool way by providing an event listener that flushes the memoised values at the end of each request lifecycle. This ensures that each request starts with a clean slate, and any memoised values are recalculated as needed. Let's take a look at how this is implemented.&lt;/p&gt;

&lt;p&gt;Octane's config file (located at &lt;code&gt;config/octane.php&lt;/code&gt;) includes a &lt;code&gt;listeners&lt;/code&gt; array where various event listeners are registered. The &lt;code&gt;Laravel\Octane\Listeners\FlushOnce&lt;/code&gt; listener is registered to listen for the &lt;code&gt;Laravel\Octane\Contracts\OperationTerminated&lt;/code&gt; event like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Octane\Contracts\OperationTerminated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Octane\Listeners\FlushOnce&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;

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

    &lt;span class="cm"&gt;/*
    |--------------------------------------------------------------------------
    | Octane Listeners
    |--------------------------------------------------------------------------
    |
    | All of the event listeners for Octane's events are defined below. These
    | listeners are responsible for resetting your application's state for
    | the next request. You may even add your own listeners to the list.
    |
    */&lt;/span&gt;

    &lt;span class="s1"&gt;'listeners'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;

        &lt;span class="nc"&gt;OperationTerminated&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;FlushOnce&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;

        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;

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

&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So when the &lt;code&gt;Laravel\Octane\Contracts\OperationTerminated&lt;/code&gt; event is fired at the end of each request, the &lt;code&gt;Laravel\Octane\Listeners\FlushOnce&lt;/code&gt; listener is executed. This listener calls the &lt;code&gt;Illuminate\Support\Once::flush&lt;/code&gt; method to clear all memoised values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Laravel\Octane\Listeners&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Once&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FlushOnce&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * Handle the event.
     *
     * @param  mixed  $event
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;class_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Once&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Once&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;flush&lt;/code&gt; method achieves this by setting the static &lt;code&gt;$instance&lt;/code&gt; property of the &lt;code&gt;Illuminate\Support\Once&lt;/code&gt; class to &lt;code&gt;null&lt;/code&gt;, effectively discarding all cached values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Illuminate\Support&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;WeakMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Once&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * The current globally used instance.
     *
     * @var static|null
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;?self&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

    &lt;span class="cd"&gt;/**
     * Create a new once instance.
     *
     * @param  \WeakMap&amp;lt;object, array&amp;lt;string, mixed&amp;gt;&amp;gt;  $values
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;WeakMap&lt;/span&gt; &lt;span class="nv"&gt;$values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Create a new once instance.
     *
     * @return static
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WeakMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

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

    &lt;span class="cd"&gt;/**
     * Flush the once instance.
     *
     * @return void
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although this is all handled automatically by Laravel Octane, it's good to understand how it works under the hood so you can be confident that your memoised values are being managed correctly. Reading vendor code like this is also a great way to &lt;a href="https://ashallendesign.co.uk/blog/how-to-improve-your-laravel-development-skills-by-reading-code" rel="noopener noreferrer"&gt;learn more about how things work internally&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Approaches to Memoisation in Laravel
&lt;/h2&gt;

&lt;p&gt;Although the &lt;code&gt;once&lt;/code&gt; helper is a great built-in way to handle memoisation in Laravel, there are other approaches you may want to take into consideration depending on your specific use case.&lt;/p&gt;

&lt;p&gt;The techniques I'll mention here might not be strictly considered "memoisation". But they can achieve similar results by holding a value that can be reused and improving performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Cache::memo" Method
&lt;/h3&gt;

&lt;p&gt;Laravel ships with a handy &lt;code&gt;memo&lt;/code&gt; method which can be used when interacting with the cache. It holds the value retrieved from the cache in memory so that subsequent calls to retrieve the same cache key within the same request do not hit the cache store again. Here's an example of how to use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// First call hits the cache store&lt;/span&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;memo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Subsequent calls return the value from memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we assume we're using Redis as a cache store, the first &lt;code&gt;get&lt;/code&gt; call would hit the Redis server to retrieve the value associated with the &lt;code&gt;key&lt;/code&gt;. However, the second call would return the value stored in memory by the &lt;code&gt;memo&lt;/code&gt; method, avoiding an additional round-trip to the Redis server. Although fetching values from Redis is usually fast, avoiding unnecessary network calls is always a good idea, especially in high-traffic applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "array" Cache Store
&lt;/h3&gt;

&lt;p&gt;Another approach you can use, which I don't see used too often, is the &lt;code&gt;array&lt;/code&gt; cache store. This driver stores cached values in an in-memory array for the duration of the request, allowing them to be reused multiple times within the same request without hitting an external cache store.&lt;/p&gt;

&lt;p&gt;I've actually written about it before in a &lt;a href="https://ashallendesign.co.uk/blog/an-introduction-to-caching-in-laravel" rel="noopener noreferrer"&gt;past blog post&lt;/a&gt;, so I won't go into too much detail here. But here's a quick example of how to use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'array'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'value'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Store value in array cache&lt;/span&gt;

&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'array'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'array'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In all fairness, I don't generally have a use case for this approach in my applications, but it's always worth knowing about the different techniques available to you. Sometimes knowing about these things can spark ideas for optimisations you might not have thought of otherwise.&lt;/p&gt;

&lt;p&gt;However, it's worth noting that the Laravel documentation specifically states that the array driver "provides a convenient cache backend for your automated tests". This suggests to me that it might not be the best choice for production applications, so use with caution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Properties and Static Properties
&lt;/h3&gt;

&lt;p&gt;Another approach to memoisation is to simply use class properties or static properties to store values that you want to reuse within the same request. This is a straightforward approach that can work well in many scenarios. In essence, this is all the &lt;code&gt;once&lt;/code&gt; helper does under the hood, except with additional management to ensure the values are scoped correctly and so on.&lt;/p&gt;

&lt;p&gt;For example, you could use a class property to hold a value that is expensive to calculate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NumberService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;?int&lt;/span&gt; &lt;span class="nv"&gt;$expensiveValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// If the value has already been calculated, return it.&lt;/span&gt;
        &lt;span class="c1"&gt;// Otherwise, calculate it and store it in the property.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;expensiveValue&lt;/span&gt; &lt;span class="o"&gt;??=&lt;/span&gt; &lt;span class="nb"&gt;random_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$numberService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NumberService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$numberService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// e.g., outputs: 42&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$numberService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;calculateExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// outputs: 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although this seems like an obvious solution to just hold the value in a property, sometimes we can overlook the simple solutions. Just remember, if you go down this route, be mindful of the scope of the property (i.e., instance vs static) and how that affects the lifetime of the cached value. You have to remember that static properties can be shared across requests when using Laravel Octane, so you'll need to manage them accordingly and reset the value at the end of each request if necessary.&lt;/p&gt;

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

&lt;p&gt;Hopefully, this article has given you a good understanding of how to use the &lt;code&gt;once&lt;/code&gt; helper in Laravel for memoisation, as well as the nuances of using it within object methods. We've also explored how Laravel Octane integrates with the &lt;code&gt;once&lt;/code&gt; helper to ensure that memoised values are reset between requests.&lt;/p&gt;

&lt;p&gt;If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "&lt;a href="https://battle-ready-laravel.com" rel="noopener noreferrer"&gt;Battle Ready Laravel&lt;/a&gt;" which covers similar topics in more depth.&lt;/p&gt;

&lt;p&gt;Or, you might want to check out my other 440+ page ebook "&lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;Consuming APIs in Laravel&lt;/a&gt;" which teaches you how to use Laravel to consume APIs from other services.&lt;/p&gt;

&lt;p&gt;If you're interested in getting updated each time I publish a new post, feel free to &lt;a href="https://ashallendesign.co.uk/blog" rel="noopener noreferrer"&gt;sign up for my newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep on building awesome stuff! 🚀&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Using AI to Build a Laravel Feature with Junie in PhpStorm</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Fri, 16 Jan 2026 15:46:25 +0000</pubDate>
      <link>https://forem.com/ashallendesign/using-ai-to-build-a-laravel-feature-with-junie-in-phpstorm-2po1</link>
      <guid>https://forem.com/ashallendesign/using-ai-to-build-a-laravel-feature-with-junie-in-phpstorm-2po1</guid>
      <description>&lt;p&gt;I've recently been giving Junie a try in PhpStorm to see how helpful it can be when working on Laravel projects.&lt;/p&gt;

&lt;p&gt;If you're not familiar with Junie, it's JetBrains' AI assistant that integrates directly into their integrated development environments (IDEs), including PhpStorm. I mentioned in a &lt;a href="https://ashallendesign.co.uk/blog/web-development-ai-2026" rel="noopener noreferrer"&gt;previous article&lt;/a&gt; that I've been using it to help me debug issues and write new code. So far, it's been a pretty positive experience. There are times when it doesn't quite get things right or misunderstands what I'm asking it to do, but overall, I've found it surprisingly useful.&lt;/p&gt;

&lt;p&gt;So I thought I'd record a video showing how to use Junie in PhpStorm. In the video, I try to use Junie to add a new settings page to a fresh Laravel application that is using the Vue.js starter kit.&lt;/p&gt;

&lt;p&gt;It's not one of those "Wow, look how amazing AI is!" types of videos (spoiler alert: it actually struggles to build what we need towards the end). But I thought it was a good demonstration of how Junie can help you build features in a Laravel application. It isn't perfect, but it can definitely speed things up when it works well.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.s - Please excuse the poor audio quality in the video. I only realised during editing that I'd recorded it using my laptop's built-in microphone rather than my usual external one. Ooops!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/9HYYpuu_nXc"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>laravel</category>
      <category>ai</category>
      <category>php</category>
    </item>
    <item>
      <title>How AI Will Impact My Job as a Freelance Web Developer in 2026</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Tue, 13 Jan 2026 22:01:18 +0000</pubDate>
      <link>https://forem.com/ashallendesign/how-ai-will-impact-my-job-as-a-freelance-web-developer-in-2026-12de</link>
      <guid>https://forem.com/ashallendesign/how-ai-will-impact-my-job-as-a-freelance-web-developer-in-2026-12de</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Over Christmas and New Year, I took a 2-week break from work to recharge my batteries. I wanted to take some time away from my laptop and avoid anything web development-related so I could have a proper mental reset. So on Monday morning last week, when I opened up my laptop and went to X for the first time in 2 weeks, I was curious to see what I’d been missing in the web development world.&lt;/p&gt;

&lt;p&gt;The first 10 or so posts that I saw on my feed were all about AI in some way. I mainly saw screenshots of agents running and people sharing that they’d managed to get the AI to build something cool for them in a short time.&lt;/p&gt;

&lt;p&gt;Then, as the week progressed, I saw that there had been layoffs (including at Laracasts and Tailwind, which was a huge shock), which were in part due to the impact of AI on the web development industry. Layoffs are the type of thing which is always sad to see. It’s hard for the employees who’ve just lost their jobs, and it’s hard for the business owners who have to make those tough decisions. I felt for everyone involved, and I don’t envy the business owners who had to make those decisions because I’m sure they weigh heavily on their minds. My heart goes out to everyone involved in those situations.&lt;/p&gt;

&lt;p&gt;But all this got me thinking about how AI will impact me as a freelance web developer in 2026, and I wanted to share my thoughts.&lt;/p&gt;

&lt;p&gt;Before we dive in, I want to say that I’m by no means an expert on AI. So there might be things in here that I get wrong, and maybe some things that I haven’t even considered. But I want to put these thoughts on paper (or screen) so that I can reflect on them later. I’d be really interested to come back to this post in a year’s time and see how my thoughts have changed (if at all).&lt;/p&gt;

&lt;h2&gt;
  
  
  My Initial Thoughts
&lt;/h2&gt;

&lt;p&gt;I’ll be completely honest, my first thoughts after reading the posts on X and seeing the layoffs were “Uh oh! Am I going to start losing work because of AI?”. I got that sinking feeling in my stomach along with a crushing wave of anxiety. After all, I need to sell my web development services to make a living and pay my bills.&lt;/p&gt;

&lt;p&gt;But after thinking about it more, I realised that AI is just another tool. And rather than seeing it as a threat, I should see it as an opportunity. After all, I’ve been using it for the past couple of years, and it’s only helped me be more productive and efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I’ve Been Using AI
&lt;/h2&gt;

&lt;p&gt;I’ve been using AI in various ways over the past couple of years to help with my workload:&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Code with GitHub Copilot
&lt;/h3&gt;

&lt;p&gt;As you’d expect, as a web developer, the most obvious way I’ve been using AI is to help me write code. Now that doesn’t mean I’ve been using it to write entire applications for me. Instead, I’ve been using &lt;a href="https://github.com/features/copilot" rel="noopener noreferrer"&gt;GitHub Copilot&lt;/a&gt; in PhpStorm to help me write short code snippets. I treat it like an autocomplete-on-steroids tool.&lt;/p&gt;

&lt;p&gt;I’ll typically get halfway through writing a line of code, and Copilot will suggest the rest of the line for me.&lt;/p&gt;

&lt;p&gt;When I first started using Copilot a few years ago, I’d say less than 50% of its suggestions were useful. I usually ignored them and just wrote the code myself. But over time, as it’s improved, I’ve found that these suggestions are exactly what I wanted to write most of the time. So now I accept the suggestions more often than not.&lt;/p&gt;

&lt;p&gt;I’ve found that when it generates entire blocks of code, the results are a bit more hit-or-miss. Sometimes it gets it right, but other times it misses the mark completely, producing bloated, over-engineered, or just plain incorrect code. But even when this happens, it still helps me because it gives me alternative approaches to solving a problem that I might not have thought of myself, which can then inspire me to come up with a better solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating Code with Agents
&lt;/h3&gt;

&lt;p&gt;I can’t give too many details away since this is related to a client’s project, but at the end of 2025, I was asked to build a new feature for a client’s application. It was the type of feature that makes you think, “Oh man! How am I going to build this?”. I kept avoiding the work and just kept picking other tasks off the to-do list instead. It’s not that I didn’t think I could build it; it’s just that I knew it would be a difficult task with a ton of edge cases to consider. I’m not sure exactly how long it would have taken me to build it myself, but I expected it would be at least a few days.&lt;/p&gt;

&lt;p&gt;But as an experiment, as a team, we used &lt;a href="https://cursor.com/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; to build the feature to see how well it would perform. We gave it a detailed brief of what we wanted the feature to do and left it to run. About 9 minutes later, it came back with a near-perfect implementation of the feature. It had even considered edge cases which we hadn’t mentioned to it ourselves.&lt;/p&gt;

&lt;p&gt;Just let me reiterate that: It built a complex feature in under 10 minutes that would have taken me days to build myself.&lt;/p&gt;

&lt;p&gt;It’s incredible!&lt;/p&gt;

&lt;p&gt;And this is what amazes me most about AI tools. I’d been feeling the dread (dread might be too strong a word, but I’m sure you get the point I’m trying to make) and anxiety of having to build this feature. I’d been thinking about different ways I could tackle it while outside of work hours, such as when taking the dog out for a walk, cooking food, or going for a run. And this AI tool just built it for us in minutes. No emotion, no stress, no anxiety. To me, I find that absolutely mind-blowing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging and Fixing Issues
&lt;/h3&gt;

&lt;p&gt;In the pre-AI days, when I encountered a bug, my workflow typically involved Googling the error message or problem I was facing. I’d then sift through the search results to find a solution. Sometimes I’d be able to find a solution quickly, but other times it would take a while to find something that fit my specific situation. As you’ll probably have experienced yourself, this can lead to a ton of open tabs in your browser as you sift through different blog posts and Stack Overflow answers trying to find the right solution.&lt;/p&gt;

&lt;p&gt;But my workflow has changed. Now, when I encounter a bug, the first thing I do is try to figure out the issue myself (just like I did before). I’ll only spend about 5 minutes on this, but I might spend more time if it means diving into vendor code (I find this particularly useful for learning how things work under the hood). If I can’t find the issue myself, I’ll then ask an AI tool, such as &lt;a href="https://www.jetbrains.com/junie/" rel="noopener noreferrer"&gt;Junie&lt;/a&gt; (the AI assistant built into PHPStorm) or ChatGPT. Finally, if neither of them can help me diagnose the issue, I’ll then Google the issue and see if I can find a solution that way on blog posts or videos.&lt;/p&gt;

&lt;p&gt;It blows my mind how much my debugging workflow has changed and how much faster I can now diagnose issues compared to before AI tools were available. I’d make a guess that at least three-quarters of the bugs I encounter these days are solved by the AI tools, so I never even make it to the Google search stage.&lt;/p&gt;

&lt;p&gt;I can’t give too many details, but I was recently trying to fix a tricky bug on a client’s application. I spent a bit of time trying to figure out what was causing it, but couldn’t seem to get to the bottom of it. So I asked Junie for help and explained what the problem was and what I expected the desired outcome to be. Within a minute or so, Junie had identified the root cause of the issue, written a full explanation of the problem (turns out it was some magic methods hidden deep in a third-party package that were interfering with the expected behaviour), and even provided a code snippet to fix the issue. I was blown away by how quickly it helped me diagnose and fix the problem.&lt;/p&gt;

&lt;p&gt;I then tested the fix, and it worked perfectly. In fact, it was so perfect that when I opened the PR to fix the issue, I copied and pasted Junie's explanation because it was so clear and concise (don’t worry, I didn’t try to claim it as my own work!). I hadn’t written a single line of code to fix the issue myself. I still think that’s crazy!&lt;/p&gt;

&lt;p&gt;Another great example of how I’ve used AI was when I was recently setting up some new app servers for my websites. I won’t bore you with the details, but I ran into a few issues related to Node and NPM versions. I explained the issue to ChatGPT, and after a bit of back-and-forth, it helped me solve the problems. It gave me the exact commands to write (including explanations of what each command did) to get everything working perfectly. Before you read that thinking “Ash, you shouldn’t be blindly copying and pasting commands from AI tools into your terminal”, don’t worry, I completely agree with you! I would have eventually figured out the commands myself if I’d written them manually, but ChatGPT just sped up the process for me. So they were completely safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Articles and my Books
&lt;/h3&gt;

&lt;p&gt;I tend to write my blog articles in PhpStorm, and I also wrote my three books (&lt;a href="https://battle-ready-laravel.com" rel="noopener noreferrer"&gt;Battle Ready Laravel&lt;/a&gt;, &lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;Consuming APIs in Laravel&lt;/a&gt;, and &lt;a href="https://web-dev-freelancing.com" rel="noopener noreferrer"&gt;The Web Dev's Guide to Freelancing&lt;/a&gt;) using it. Now, that might seem like a weird choice, but let me explain why.&lt;/p&gt;

&lt;p&gt;The content of my articles and books is all written as Markdown files. I then copy the contents of the Markdown files into my blog’s backend or use external tools to convert them into PDFs and EPUBs for my books.&lt;/p&gt;

&lt;p&gt;I start by writing my code examples, then add bullet points in each section outlining what I want to cover. Once I’ve got that rough outline, I go through the sections one by one and expand the bullet points to turn them into full paragraphs. This is the approach I’ve used for doing any writing, going all the way back to when I was 15-16 years old in school. It’s an approach that works well for me because it allows me to get my main talking points down quickly without getting bogged down in the details of writing full sentences and paragraphs.&lt;/p&gt;

&lt;p&gt;However, I’ve found that AI tools have helped me improve this process even more. For example, let’s say I have a bullet point but don’t know how to turn it into a full sentence or paragraph (sometimes finding the right way to start a sentence is the hardest part!). Since I’m writing in PhpStorm, I can let Copilot auto-suggest the beginning of the sentence for me. For me, this is a huge win because it helps me overcome writer’s block and get the words flowing more easily.&lt;/p&gt;

&lt;p&gt;By no means would I ever use it to write entire articles or books for myself. I still want to be the one writing the content because I want it to be my voice and my style. Heck, I feel like a “cheater” and a “fake” when I let it write just a couple of words to start a sentence. But having AI tools help me with the small bits of writing here and there (such as helping me to start a tricky sentence) has made the process much smoother and more enjoyable. This means I can produce more content in less time, which is a win-win for both my readers and me.&lt;/p&gt;

&lt;h2&gt;
  
  
  How My Job Might Change in 2026 and Beyond
&lt;/h2&gt;

&lt;p&gt;As I said at the beginning of this article, my initial thoughts at the beginning of this year were ones of anxiety and fear about how AI might impact my job as a freelance web developer.&lt;/p&gt;

&lt;p&gt;But after spending time reflecting on it, I’ve realised that AI is just another tool for me to use. I strongly believe that the type of work I do (or at least the workflow I use) will change significantly over the next few years as AI tools become more advanced and capable. And, to be honest, I find it quite exciting!&lt;/p&gt;

&lt;h3&gt;
  
  
  As a Freelance Web Developer
&lt;/h3&gt;

&lt;p&gt;Whenever I speak to developers who were writing code in the early 2000s or late 90s, I hear things like “Back then, we had to do [SOMETHING SERIOUSLY LONG WINDED AND TEDIOUS] manually”. Then I’d reply saying, “Wow, that sounds awful! Now we can do it with a single line of code”.&lt;/p&gt;

&lt;p&gt;I have a strong feeling that, in a few years' time, developers will be saying the same thing about AI. They’ll look back and think, “I can’t believe we used to write all that code ourselves manually. What a waste of time!”.&lt;/p&gt;

&lt;p&gt;To what extent this will happen, I don’t know. Maybe I’ll continue using AI tools as an autocomplete tool, or maybe I’ll start using agents that can build entire features for me. But either way, I have a feeling that my role as a web developer is going to shift quite a bit.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pricing My Work
&lt;/h4&gt;

&lt;p&gt;As I’ve mentioned, I’ve already played around with using Junie for writing code and debugging issues, and it’s felt like it’s given me superpowers. I’ve been able to deliver features faster and fix bugs more quickly than ever before. This is absolutely fantastic for my clients and me because it means I can take on more work and deliver it faster.&lt;/p&gt;

&lt;p&gt;But this introduces a minor business issue for me. At the time of writing this article, I charge &lt;a href="https://ashallendesign.co.uk/services/laravel-web-development" rel="noopener noreferrer"&gt;£60/hour for any new freelancing work&lt;/a&gt; I take on. Let’s say, for example, that I could build a feature in 8 hours without AI assistance; that would cost the client £480. But if I use an AI agent to build the feature for me, and it only takes me 2 hours to review and test the code, that would only cost the client £120. This is a huge saving for the client (which is awesome for them!), but it also means that my revenue from that work has dropped significantly.&lt;/p&gt;

&lt;p&gt;Every freelance developer hits these issues as they get more efficient at what they do. We raise our prices as we get faster over time to compensate for the fact that we spend less time doing the same amount of work. It’s actually something I talk about in the “How Much Should I Charge?” section of &lt;a href="https://web-dev-freelancing.com" rel="noopener noreferrer"&gt;The Web Dev’s Guide to Freelancing&lt;/a&gt;, where I talk about making sure you get paid a fair amount. But with AI, the speed increases will be exponential. Rather than being able to build a feature twice as fast, I might be able to build it 4x or even 10x faster. Does this mean I’d need to increase my hourly rate to £600/hour to maintain the same revenue? Absolutely not, that’s crazy!&lt;/p&gt;

&lt;p&gt;So this is where I’m probably going to need to rethink my pricing and business model. For long-term contracts where I’ve been hired to provide a given number of hours per week/month and pick issues/features off a Trello/Linear board to implement, I can likely continue charging by the hour. It just means I’ll get through the work faster than before and can move on to the next feature sooner. So nothing really changes there.&lt;/p&gt;

&lt;p&gt;But for fixed-price projects, I might need to tweak my pricing strategy. Usually, I’d take a rough estimate of how long I think it will take me to build a feature, then multiply that by my hourly rate to get a fixed price for the client. I’ll then make any tweaks to the price based on the “value” that the work is providing for the client. We need to remember, when pricing, that we’re not just selling our time; we’re selling the value our work provides to the client.&lt;/p&gt;

&lt;p&gt;But with AI assistance, since I’ll be building them faster than before, I might need to adjust how I price these types of projects. Maybe I’ll need to reduce the weight my time has in the overall price and increase the weight the value has. This way, even if I build the feature in a quarter of the time, I can still charge a fair price that reflects the value the feature provides to the client. You could argue that I should already be pricing this way in the first place, but it’s an approach that’s worked for me so far, so I haven’t felt the need to change it until now because it provides me with a predictable, consistent flow of work and income.&lt;/p&gt;

&lt;h4&gt;
  
  
  Switching from Code Writer to Code Reviewer
&lt;/h4&gt;

&lt;p&gt;As I’ve already mentioned, AI tools can generate some pretty good code. But I’ve also seen it produce bloated, over-engineered code that needs significant refactoring. I’ve also seen it produce code that looks fine on the surface but misses some edge cases that could lead to bugs later. It’d be easy for someone to blindly accept the AI-generated code without fully reviewing it (or understanding what it’s doing) and miss these issues. But as an experienced developer, I know what to look for and can catch these issues before they reach production. And it’s easy to miss these types of edge cases when you’re reviewing AI-generated code because you’re not as deeply immersed in the problem as you would be if you were writing the code yourself.&lt;/p&gt;

&lt;p&gt;I used Junie this week to generate the boilerplate code for a new feature I was building for a client’s project. It wrote 331 lines of code for me, and I’d say it got the feature 50% of the way there. I’d guess it saved me roughly 2-3 hours of development time. I then manually wrote the final 50% of the code and tweaked the generated code. I noticed that, of the 331 lines, around 40-50 were unnecessary and could be removed and simplified. These lines weren’t necessarily wrong, and they did achieve the desired outcome when run, but they were just completely overcomplicated. So if we were to imagine a scenario where we blindly accepted the suggestions, and we added 10 new features, that’d result in 400-500 unneeded lines of code. That’s extra code to maintain, extra code to write tests for, and extra code which could introduce potential bugs or vulnerabilities.&lt;/p&gt;

&lt;p&gt;So I think the value I can provide is knowing how to use AI tools effectively. Mainly by knowing what to ask the AI, how to review the generated code and ensure that it actually meets the requirements without introducing bugs or security issues, and how to test the code properly to ensure that it works as expected.&lt;/p&gt;

&lt;p&gt;For these reasons, I have a feeling my workflow will change over the coming years, from being a code writer to more of a code reviewer. I’ll still be writing some code myself and building features, but just nowhere near as much as I do now.&lt;/p&gt;

&lt;p&gt;I don’t know if that’s a good thing or a bad thing. I really enjoy writing code and solving problems. It’s such a good feeling when I’ve been spending hours trying to get a particular feature working exactly how I want it, and then it finally starts working perfectly. It gives me a huge sense of satisfaction and feels like an achievement.&lt;/p&gt;

&lt;p&gt;But I don’t get that same feeling when I’m generating code. Don’t get me wrong, I’m still absolutely amazed when Junie writes some code for me in minutes that would have taken me hours to build myself. But I don’t get that same sense of achievement because I didn’t actually write the code myself. The AI did that for me.&lt;/p&gt;

&lt;p&gt;At the end of the day, though, my goal is to deliver value to my clients. If using AI tools helps me deliver features faster and more efficiently, then I’m all for it. Clients don’t care how the code gets written; they just care that it works and meets their needs.&lt;/p&gt;

&lt;p&gt;I just hope that I don’t “forget” how to write code manually because I’m relying on AI tools too much. I want to make sure I still have the skills and knowledge to write code myself if needed. I have a feeling I might keep writing code manually for my open-source packages and personal projects just so I can keep my skills sharp.&lt;/p&gt;

&lt;h4&gt;
  
  
  Fixing AI-Generated Issues
&lt;/h4&gt;

&lt;p&gt;Currently, I’m hired quite often by teams who get in touch and say, “We have a load of bugs in our code that we need fixing. Can you help?”. These are usually bugs introduced over years of development and that have built up over time. I then join the project and get to work with fixing the issues, writing tests, and improving the overall code quality.&lt;/p&gt;

&lt;p&gt;I’m wondering whether AI will increase these types of enquiries for me. As I mentioned, it’s really easy to generate bad code if you don’t know what you’re looking for. So I wonder whether more projects will start to see an increase in bugs, issues, and spaghetti code because of poorly implemented AI-generated code. If this happens, I could see more teams reaching out to me to help them fix these issues.&lt;/p&gt;

&lt;p&gt;Of course, this might not happen if the AI tools get better at generating high-quality code or fixing issues themselves. But I have a feeling that there will always be a need for experienced developers who can review and fix AI-generated code.&lt;/p&gt;

&lt;h3&gt;
  
  
  As a Content Creator
&lt;/h3&gt;

&lt;p&gt;As I’ve mentioned, my workflow for solving issues has changed significantly because of AI tools. Blogs, videos, and Stack Overflow posts used to be my go-to resources for finding solutions to problems. But now, I turn to AI tools first because they can provide tailored solutions specific to my situation without leaving my IDE. And I suspect I’m not the only one doing this.&lt;/p&gt;

&lt;p&gt;I’ve seen a huge decrease in the visitors to my blog over the past year or so. Of course, this could be down to many different factors, such as changes in search engine algorithms. But from reading online, it seems many other content creators are seeing similar drops in traffic. And the general consensus is that AI tools are a major reason.&lt;/p&gt;

&lt;p&gt;So I think the type of content I create will need to change over the coming years. Rather than writing blog posts that provide solutions to specific problems, I might need to focus more on creating content that shows readers new things. For example, let’s say you want to add some JSON Web Tokens (JWT) to your application. You could likely ask an AI tool to generate the code for you. But to be able to ask the AI, you need to know in the first place that JWTs are even a thing. After all, you don’t know what you don’t know.&lt;/p&gt;

&lt;p&gt;For this reason, I think my content will need to focus more on introducing readers to new concepts and ideas they might not have heard of before. Then they can take that knowledge and use AI tools to help them implement those concepts in their own projects. Or, maybe I’ll write more opinion pieces if that’s something that people find valuable.&lt;/p&gt;

&lt;p&gt;Just like with code, I really enjoy writing articles and content, so even if the traffic completely dries up, I don’t think I’ll stop writing. I like writing about things I’ve just learnt so I can refer back to them later. I just might need to adjust my expectations about how much traffic my content will get in the future.&lt;/p&gt;

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

&lt;p&gt;If you’ve made it this far, thank you for sticking with me and my rambling thoughts. I’d be really interested to hear your thoughts on how AI is going to impact your job in 2026 and beyond. Do you think it’s going to be a big change, or do you think things will stay relatively the same?&lt;/p&gt;

&lt;p&gt;As I say, I’m by no means an expert on AI, so there might be things that I’ve missed or got wrong. But I wanted to make sure I jotted down my thoughts because I think 2026 is going to be a scary but exciting year for web developers like us.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>freelance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Formatting PHP Code with PHP CS Fixer</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Thu, 18 Dec 2025 16:37:43 +0000</pubDate>
      <link>https://forem.com/ashallendesign/formatting-php-code-with-php-cs-fixer-35p</link>
      <guid>https://forem.com/ashallendesign/formatting-php-code-with-php-cs-fixer-35p</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Maintaining a consistent code style is a key aspect of software and web development. It improves code readability, reduces developers' cognitive load, and ensures everyone on the team sticks to the same standards. &lt;/p&gt;

&lt;p&gt;But manually formatting code style is time-consuming, tedious, and error-prone. It's possible to format your code automatically using your integrated development environment (IDE) or text editor, but this approach has its limitations. Not all team members may use the same tools, and IDE configurations can vary widely. So it's usually better to use a dedicated code style tool that can be run consistently across different environments and in your continuous integration (CI) pipeline.&lt;/p&gt;

&lt;p&gt;One of the tools you can use for formatting your PHP code is &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer" rel="noopener noreferrer"&gt;PHP CS Fixer&lt;/a&gt;. It's an incredibly popular tool and, at the time of writing, has over 214 million downloads on Packagist.&lt;/p&gt;

&lt;p&gt;In this article, we'll look at how to install and configure PHP CS Fixer. We'll then explore how to run it locally and in parallel mode. Finally, I'll show you how to create a GitHub Actions workflow to automate code style checks on pull requests using PHP CS Fixer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing PHP CS Fixer
&lt;/h2&gt;

&lt;p&gt;To get started with PHP CS Fixer, you'll first need to install it via Composer. You can do this by running the following command in your project's root directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require friendsofphp/php-cs-fixer --dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the Config File
&lt;/h2&gt;

&lt;p&gt;After it finishes installing, you'll then need to create a configuration file for PHP CS Fixer. This file allows you to specify the coding standards you want to enforce, the directory you want to run the tool against, and any custom rules you may have.&lt;/p&gt;

&lt;p&gt;To do this, you'll need to create a &lt;code&gt;.php-cs-fixer.dist.php&lt;/code&gt; file in the root directory of your project.&lt;/p&gt;

&lt;p&gt;Let's look at an example configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PhpCsFixer\Config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PhpCsFixer\Finder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setRules&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'@PSR12'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setFinder&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Finder&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the config file above, we are specifying that we want to use the PSR-12 coding standard and that we want to scan all files in the current directory (and child directories) for code style issues.&lt;/p&gt;

&lt;p&gt;If you want to run the tool against specific directories, you can modify the &lt;code&gt;setFinder&lt;/code&gt; method. For example, let's imagine we only want to run the tool against the &lt;code&gt;src&lt;/code&gt; and &lt;code&gt;tests&lt;/code&gt; directories. We can do this by updating the &lt;code&gt;setFinder&lt;/code&gt; method as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PhpCsFixer\Config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PhpCsFixer\Finder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setRules&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'@PSR12'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setFinder&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Finder&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;in&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/src'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/tests'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a ton of configuration options and rules available for PHP CS Fixer, and I won't dive into them all here. But I'd recommend checking out the documentation on GitHub so you can find any other rules you may wish to use: &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/rules/index.rst" rel="noopener noreferrer"&gt;https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/rules/index.rst&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formatting Code with PHP CS Fixer
&lt;/h2&gt;

&lt;p&gt;Now that we have PHP CS Fixer installed and configured, we can run it against our codebase.&lt;/p&gt;

&lt;p&gt;Typically, there are two commands you'll want to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./vendor/bin/php-cs-fixer check&lt;/code&gt; - This command checks your code for any style issues based on the rules defined in your configuration file. It will output a list of files that do not comply with the specified coding standards.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./vendor/bin/php-cs-fixer fix&lt;/code&gt; - This command automatically fixes any code style issues found in your codebase based on the rules defined in your configuration file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if we wanted to run the &lt;code&gt;check&lt;/code&gt; command to see if there are any code style issues in our project, we would run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vendor/bin/php-cs-fixer check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this would generate output similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ ./vendor/bin/php-cs-fixer check
PHP CS Fixer 3.89.1 Folding Bike by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.4.2
Running analysis on 1 core sequentially.
You can enable parallel runner and speed up the analysis! Please see usage docs for more information.
Loaded config default from "/Users/ashallen/www/open-source/packages/email-utilities/.php-cs-fixer.dist.php".
Using cache file ".php-cs-fixer.cache".
 20/20 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

   1) tests/Feature/Email/DomainIsNotTest.php
   2) src/Email.php

Found 2 of 20 files that can be fixed in 0.026 seconds, 20.00 MB memory used
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, to automatically fix the issues found, we can run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vendor/bin/php-cs-fixer fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this command would fix any of the code style issues found in our project and generate an output like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ ./vendor/bin/php-cs-fixer fix  
PHP CS Fixer 3.89.1 Folding Bike by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.4.2
Running analysis on 1 core sequentially.
You can enable parallel runner and speed up the analysis! Please see usage docs for more information.
Loaded config default from "/Users/ashallen/www/open-source/packages/email-utilities/.php-cs-fixer.dist.php".
Using cache file ".php-cs-fixer.cache".
 20/20 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

   1) tests/Feature/Email/DomainIsNotTest.php
   2) src/Email.php

Fixed 2 of 20 files in 0.029 seconds, 20.00 MB memory use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running PHP CS Fixer in Parallel Mode
&lt;/h2&gt;

&lt;p&gt;PHP CS Fixer allows running checks and fixes in parallel. This can significantly speed up the process, especially for larger codebases.&lt;/p&gt;

&lt;p&gt;You can enable parallel mode by updating your configuration file to include the &lt;code&gt;setParallelConfig&lt;/code&gt; method. Here's an example of how to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PhpCsFixer\Config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PhpCsFixer\Finder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setRules&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'@PSR12'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setFinder&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Finder&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setParallelConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PhpCsFixer\Runner\Parallel\ParallelConfigFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we're letting PHP CS Fixer automatically detect the optimal parallel configuration for our system. But you can manually specify these details if you wish.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;./vendor/bin/php-cs-fixer fix&lt;/code&gt; will then run the fixer in parallel mode. You should see output similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ ./vendor/bin/php-cs-fixer fix  
PHP CS Fixer 3.89.1 Folding Bike by Fabien Potencier, Dariusz Ruminski and contributors.
PHP runtime: 8.4.2
Running analysis on 7 cores with 10 files per process.
Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!
Loaded config default from "/Users/ashallen/www/open-source/packages/email-utilities/.php-cs-fixer.dist.php".
Using cache file ".php-cs-fixer.cache".
 20/20 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

   1) src/Email.php

Fixed 1 of 20 files in 0.210 seconds, 20.00 MB memory used
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running PHP CS Fixer using GitHub Actions
&lt;/h2&gt;

&lt;p&gt;A great way to ensure that your code style remains consistent across your team is to automate the code style checks. Doing this helps catch any instances where you forget to run the code formatter locally before pushing your changes.&lt;/p&gt;

&lt;p&gt;To automate this process, I like to use a GitHub Actions workflow that runs on every pull request. Since PHP CS Fixer returns a non-zero exit code (indicating failure) if any code style issues are found, we can rely on this to fail the workflow.&lt;/p&gt;

&lt;p&gt;Let's look at a typical GitHub Actions workflow you may want to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Code Style&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;php-cs-fixer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update apt&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo apt-get update --fix-missing&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup PHP&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shivammathur/setup-php@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;php-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8.4&lt;/span&gt;
          &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;composer install&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run PHP CS Fixer&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./vendor/bin/php-cs-fixer check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we can see that we're running the &lt;code&gt;./vendor/bin/php-cs-fixer check&lt;/code&gt; command as part of the workflow. If any code style issues are found, the command will return a non-zero exit code, causing the workflow to fail. If the command passes, it will return a zero exit code, and the workflow will succeed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I Fix Issues Automatically in the Workflow?
&lt;/h3&gt;

&lt;p&gt;You may want to run the &lt;code&gt;fix&lt;/code&gt; command and automatically commit the changes to the pull request. However, I generally avoid this approach because it can introduce unexpected changes to the codebase without my knowledge. Instead, I prefer to run the &lt;code&gt;check&lt;/code&gt; command and then fix any issues locally. This way, I have full control over the changes being made to the codebase.&lt;/p&gt;

&lt;p&gt;This is only my personal opinion, though, and you may find that automatically fixing issues in the workflow works better for your team. If you do want to fix the issues in your workflow automatically, you might want to consider opening a pull request via your workflow with the updated code style changes instead of committing them directly to the branch. This way, you can review the changes before they are merged into the main codebase.&lt;/p&gt;

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

&lt;p&gt;In this article, we explored how to set up and use PHP CS Fixer for maintaining code style in your PHP projects. We covered installation, configuration, running it locally and in parallel mode, and automating code style checks using GitHub Actions. By integrating PHP CS Fixer into your development workflow, you can ensure consistent code quality and adherence to coding standards across your projects.&lt;/p&gt;

&lt;p&gt;If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "&lt;a href="https://battle-ready-laravel.com" rel="noopener noreferrer"&gt;Battle Ready Laravel&lt;/a&gt;" which covers similar topics in more depth.&lt;/p&gt;

&lt;p&gt;Or, you might want to check out my other 440+ page ebook "&lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;Consuming APIs in Laravel&lt;/a&gt;" which teaches you how to use Laravel to consume APIs from other services.&lt;/p&gt;

&lt;p&gt;If you're interested in getting updated each time I publish a new post, feel free to &lt;a href="https://ashallendesign.co.uk/blog" rel="noopener noreferrer"&gt;sign up for my newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep on building awesome stuff! 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>tooling</category>
      <category>ci</category>
    </item>
    <item>
      <title>The Difference Between ?: and ?? in PHP</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Mon, 24 Nov 2025 16:30:00 +0000</pubDate>
      <link>https://forem.com/ashallendesign/the-difference-between-and-in-php-310o</link>
      <guid>https://forem.com/ashallendesign/the-difference-between-and-in-php-310o</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In PHP, I often see the ternary operator (&lt;code&gt;?:&lt;/code&gt;) and null coalescing operator (&lt;code&gt;??&lt;/code&gt;) being used interchangeably, but they actually serve different purposes.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore what both of these operators do and how they differ. We'll also look at some common gotchas that developers (myself included) have run into when using these operators.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ternary Operator (?:) in PHP
&lt;/h2&gt;

&lt;p&gt;The ternary operator is a shorthand way of writing a ternary conditional statement.&lt;/p&gt;

&lt;p&gt;For example, let's take a traditional if-else statement. We'll keep the examples in this article super simple purely for brevity.&lt;/p&gt;

&lt;p&gt;Imagine we're trying to set a display name based on a provided name in a payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Imagine this payload comes from an HTTP request.&lt;/span&gt;
&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ash'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// $displayName is now set to 'Ash'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can rewrite this using a ternary condition like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ash'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Ash'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which can then be further shortened using the &lt;code&gt;?:&lt;/code&gt; operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ash'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Ash'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's look at some examples of what &lt;code&gt;$displayName&lt;/code&gt; would be set to based on different values of &lt;code&gt;$payload['name']&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the payload contains a non-empty string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ash'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Ash'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the request payload contains an empty string or &lt;code&gt;null&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Guest'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the payload contains &lt;code&gt;null&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Guest'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the payload contains &lt;code&gt;false&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Guest'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, what would happen if the &lt;code&gt;payload&lt;/code&gt; array didn't have a &lt;code&gt;name&lt;/code&gt; key at all?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the above code would result in the following exception being thrown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ErrorException: Undefined array key "name"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we can see that the ternary operator checks for "truthiness" of the value. If the value is falsy (such as an empty string or &lt;code&gt;null&lt;/code&gt;), it returns the value on the right side of the operator.&lt;/p&gt;

&lt;p&gt;However, if the variable is not set at all, it will throw an error. And this is where I often see people get confused between the &lt;code&gt;?:&lt;/code&gt; and &lt;code&gt;??&lt;/code&gt; operators. I've seen many developers expect that the &lt;code&gt;?:&lt;/code&gt; operator would handle unset variables gracefully, but it does not. So let's check out the null coalescing operator next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Null Coalescing Operator (??) in PHP
&lt;/h2&gt;

&lt;p&gt;The null coalescing operator works in a similar way to the ternary operator, but it allows us to handle unset variables without throwing an error.&lt;/p&gt;

&lt;p&gt;Let's take our previous example and update it to use the &lt;code&gt;??&lt;/code&gt; operator instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ash'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Ash'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code example is essentially the shorthand form of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ash'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Ash'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, we can use the &lt;code&gt;??&lt;/code&gt; operator in a similar way to the &lt;code&gt;?:&lt;/code&gt; operator, but it's checking whether the variable is set or not, rather than checking for "truthiness".&lt;/p&gt;

&lt;p&gt;Let's see what happens if we try to access the &lt;code&gt;name&lt;/code&gt; key when it is not set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Guest'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code example, we can see that when the &lt;code&gt;name&lt;/code&gt; key is not set in the &lt;code&gt;payload&lt;/code&gt; array, the &lt;code&gt;??&lt;/code&gt; operator returns the value on the right side of the operator without throwing an error.&lt;/p&gt;

&lt;p&gt;This can be really useful when working with data that's passed to you from external sources, such as API requests or user input, where you can't always guarantee that certain keys will be present.&lt;/p&gt;

&lt;p&gt;Another key difference between the &lt;code&gt;??&lt;/code&gt; and &lt;code&gt;?:&lt;/code&gt; operators is that the &lt;code&gt;??&lt;/code&gt; operator only checks for &lt;code&gt;null&lt;/code&gt; or unset values, whereas the &lt;code&gt;?:&lt;/code&gt; operator checks for "truthiness". Let's take this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Using the ternary operator:&lt;/span&gt;
&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Guest'&lt;/span&gt;

&lt;span class="c1"&gt;// Using the null coalescing operator:&lt;/span&gt;
&lt;span class="nv"&gt;$displayName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see in the example above, when the &lt;code&gt;name&lt;/code&gt; key is set to &lt;code&gt;false&lt;/code&gt;, the &lt;code&gt;?:&lt;/code&gt; operator returns &lt;code&gt;'Guest'&lt;/code&gt;, whereas the &lt;code&gt;??&lt;/code&gt; operator returns &lt;code&gt;false&lt;/code&gt;. This is something you'll want to keep in mind when deciding which operator to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Gotcha with the Null Coalescing Operator
&lt;/h3&gt;

&lt;p&gt;A common gotcha I see developers run into (and have done myself many times) is accidentally hiding errors when using the &lt;code&gt;??&lt;/code&gt; operator.&lt;/p&gt;

&lt;p&gt;For example, let's say you have an API request payload that is expected to contain a &lt;code&gt;first_name&lt;/code&gt; key, but you accidentally misspell it as &lt;code&gt;frist_name&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'first_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ash'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'frist_name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 'Guest'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this code might seem okay since it doesn't throw any runtime errors. However, we will always get &lt;code&gt;'Guest'&lt;/code&gt; as the value of &lt;code&gt;$firstName&lt;/code&gt;. Of course, this should typically be caught during manual and automated testing. But these things can sometimes slip through the cracks, especially in larger codebases.&lt;/p&gt;

&lt;p&gt;Alternatively, if we had used the ternary operator instead, we would have gotten an error when trying to access the unset &lt;code&gt;first_name&lt;/code&gt; key, which would have alerted us to the typo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'first_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Ash'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'frist_name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;'Guest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ErrorException: Undefined array key "first_name"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Should You Use the Ternary or Null Coalescing Operator?
&lt;/h2&gt;

&lt;p&gt;Both operators have valid use cases and are valuable tools in your PHP arsenal.&lt;/p&gt;

&lt;p&gt;If you always know that a value will be available (i.e. it won't be unset), and you want to check for "truthiness", then the ternary operator (&lt;code&gt;?:&lt;/code&gt;) is a good choice.&lt;/p&gt;

&lt;p&gt;However, if you want to handle unset variables gracefully, or you only want to check for &lt;code&gt;null&lt;/code&gt; or unset values, then the null coalescing operator (&lt;code&gt;??&lt;/code&gt;) might be the better option.&lt;/p&gt;

&lt;p&gt;As you'd imagine, the decision of which operator to use often comes down to the specific use case and the data you're working with. So you'll likely use a mixture of both operators in your codebase.&lt;/p&gt;

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

&lt;p&gt;In this article, we've quickly explored the differences between the &lt;code&gt;?:&lt;/code&gt; (ternary) and &lt;code&gt;??&lt;/code&gt; (null coalescing) operators in PHP.&lt;/p&gt;

&lt;p&gt;If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "&lt;a href="https://battle-ready-laravel.com" rel="noopener noreferrer"&gt;Battle Ready Laravel&lt;/a&gt;" which covers similar topics in more depth.&lt;/p&gt;

&lt;p&gt;Or, you might want to check out my other 440+ page ebook "&lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;Consuming APIs in Laravel&lt;/a&gt;" which teaches you how to use Laravel to consume APIs from other services.&lt;/p&gt;

&lt;p&gt;If you're interested in getting updated each time I publish a new post, feel free to &lt;a href="https://ashallendesign.co.uk/blog" rel="noopener noreferrer"&gt;sign up for my newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep on building awesome stuff! 🚀&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Black Friday 2025 - Up to 50% off my books!</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Mon, 24 Nov 2025 11:51:14 +0000</pubDate>
      <link>https://forem.com/ashallendesign/black-friday-2025-up-to-50-off-my-books-7cg</link>
      <guid>https://forem.com/ashallendesign/black-friday-2025-up-to-50-off-my-books-7cg</guid>
      <description>&lt;p&gt;Hey there!&lt;/p&gt;

&lt;p&gt;I'm going to keep this short and sweet (I know how busy our inboxes get in November from all the Black Friday deals).&lt;/p&gt;

&lt;p&gt;This year for Black Friday, I'm offering up to 50% off my books (including the bundles, which give you even more savings):&lt;/p&gt;

&lt;h2&gt;
  
  
  30% off "Battle Ready Laravel"
&lt;/h2&gt;

&lt;p&gt;I'm offering 30% off my book, Battle Ready Laravel.&lt;/p&gt;

&lt;p&gt;This book is the guide to auditing, testing, fixing, and improving your Laravel applications. It has sold over 1,300 copies and is packed with a ton of actionable advice, tips, tricks, and real-life examples to help you level up your Laravel skills.&lt;/p&gt;

&lt;p&gt;Code: &lt;strong&gt;30BLACKFRIDAY2025&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Get it here: &lt;a href="https://battle-ready-laravel.com" rel="noopener noreferrer"&gt;https://battle-ready-laravel.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  30% off "Consuming APIs in Laravel"
&lt;/h2&gt;

&lt;p&gt;I'm also offering 30% off my book, Consuming APIs in Laravel.&lt;/p&gt;

&lt;p&gt;This book teaches you how to build awesome, robust, testable API integrations for your Laravel application using Saloon. It also breaks down complex topics such as API authentication, OAuth 2.0, API integration testing, and webhooks.&lt;/p&gt;

&lt;p&gt;I'm always referring back to this book whenever I'm working on API integrations for my clients.&lt;/p&gt;

&lt;p&gt;Code: &lt;strong&gt;30BLACKFRIDAY2025&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Get it here: &lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;https://consuming-apis-in-laravel.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  50% off "The Web Dev's Guide to Freelancing"
&lt;/h2&gt;

&lt;p&gt;Finally, I'm offering 50% off my latest book, The Web Dev's Guide to Freelancing.&lt;/p&gt;

&lt;p&gt;This book teaches you how to get started with building a successful freelancing business as a web developer. It covers everything from finding clients, setting rates, managing projects, and scaling your business.&lt;/p&gt;

&lt;p&gt;Code: &lt;strong&gt;50BLACKFRIDAY2025&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Get it here: &lt;a href="https://web-dev-freelancing.com" rel="noopener noreferrer"&gt;https://web-dev-freelancing.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>books</category>
    </item>
    <item>
      <title>Immutable and Mutable Dates in PHP</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Thu, 20 Nov 2025 15:30:00 +0000</pubDate>
      <link>https://forem.com/ashallendesign/immutable-and-mutable-dates-in-php-h60</link>
      <guid>https://forem.com/ashallendesign/immutable-and-mutable-dates-in-php-h60</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When working with dates in PHP, it's important to understand the difference between mutable and immutable date objects. PHP provides two main classes for handling dates: &lt;code&gt;DateTime&lt;/code&gt; (mutable) and &lt;code&gt;DateTimeImmutable&lt;/code&gt; (immutable).&lt;/p&gt;

&lt;p&gt;Similarly, if you're using the Carbon library, it also provides both mutable (&lt;code&gt;Carbon\Carbon&lt;/code&gt;) and immutable (&lt;code&gt;Carbon\CarbonImmutable&lt;/code&gt;) date classes.&lt;/p&gt;

&lt;p&gt;There are some key differences between mutable and immutable date objects. And I've run into bugs more often than I'd like to admit because I didn't fully understand these differences at the time, or didn't realise which type was being used in a particular situation.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore the differences between mutable and immutable date objects in PHP and the Carbon library. We'll also look at examples of how each type behaves and explore how to use immutable date objects by default in a Laravel application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutable Dates in PHP
&lt;/h2&gt;

&lt;p&gt;Mutable date objects are objects which can be modified after they are created. This means that any changes made to a mutable date object will affect the original object.&lt;/p&gt;

&lt;p&gt;For example, let's look at the following code using &lt;code&gt;DateTime&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2025-01-01'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;modify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'+1 day'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we've created a &lt;code&gt;DateTime&lt;/code&gt; object representing 1st January 2025. We then called the &lt;code&gt;modify&lt;/code&gt; method to add one day. As we can see, this changed the original &lt;code&gt;$date&lt;/code&gt; object to represent 2nd January 2025.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Danger of Using Mutable Dates
&lt;/h3&gt;

&lt;p&gt;Since the original object is updated when copied mutable dates are updated, using them can sometimes lead to unintended side effects, particularly when passing date objects around in your code.&lt;/p&gt;

&lt;p&gt;Let's take a look at an example, and then we'll discuss what's going on. We'll create a mutable &lt;code&gt;DateTime&lt;/code&gt; object for &lt;code&gt;2025-01-01&lt;/code&gt;. We'll then create another variable that references the same &lt;code&gt;DateTime&lt;/code&gt; object and add one day to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2025-01-01'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$anotherDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$anotherDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;modify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'+1 day'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-02&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$anotherDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code example above, we can see that adding 1 day to &lt;code&gt;$anotherDate&lt;/code&gt; also affects &lt;code&gt;$date&lt;/code&gt;. This is because both variables reference the same mutable &lt;code&gt;DateTime&lt;/code&gt; object. This can lead to bugs that are hard to track down, especially in larger codebases.&lt;/p&gt;

&lt;p&gt;I can't tell you how many times this behaviour has bitten me in the past! I've spent hours debugging issues that were caused by unintended modifications to mutable date objects.&lt;/p&gt;

&lt;p&gt;Typically, I've found the best approach to solving this issue is to use immutable date objects instead. However, if you must use mutable dates (maybe you're working with a large codebase that would require a lot of refactoring), you might want to consider cloning the date object before making modifications instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2025-01-01'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$anotherDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;clone&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$anotherDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;modify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'+1 day'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-01&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$anotherDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see in the code example, rather than using &lt;code&gt;$anotherDate = $date;&lt;/code&gt;, we use &lt;code&gt;clone&lt;/code&gt; to create a copy of the original date object. This way, modifications to &lt;code&gt;$anotherDate&lt;/code&gt; do not affect &lt;code&gt;$date&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mutable Dates in Carbon
&lt;/h3&gt;

&lt;p&gt;If you're using the &lt;code&gt;Carbon&lt;/code&gt; library for date handling, the same principles apply. Here's an example using &lt;code&gt;Carbon\Carbon&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Carbon\Carbon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Carbon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$anotherDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$anotherDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addDay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-02&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$anotherDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Carbon provides a handy &lt;code&gt;-&amp;gt;copy()&lt;/code&gt; method to create a clone of a mutable date object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Carbon\Carbon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Carbon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$anotherDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$anotherDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addDay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-01&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$anotherDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Immutable Dates in PHP
&lt;/h2&gt;

&lt;p&gt;Immutable date objects are date objects that cannot be modified after they are created. Any changes made to an immutable date object will result in a new object being created, leaving the original object unchanged.&lt;/p&gt;

&lt;p&gt;In my opinion, using immutable date objects is generally the safer and more predictable approach. I've found that it helps avoid some of the pitfalls we just discussed with mutable dates. That doesn't mean mutable dates don't have their place; they can still be helpful in certain scenarios.&lt;/p&gt;

&lt;p&gt;You can create an immutable date object using &lt;code&gt;DateTimeImmutable&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTimeImmutable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2025-01-01'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$newDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;modify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'+1 day'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-01&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$newDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code example, we created a &lt;code&gt;DateTimeImmutable&lt;/code&gt; object for &lt;code&gt;2025-01-01&lt;/code&gt;. When we added a day using the &lt;code&gt;modify&lt;/code&gt; method, it returned a new &lt;code&gt;DateTimeImmutable&lt;/code&gt; object (&lt;code&gt;$newDate&lt;/code&gt;) without changing the original &lt;code&gt;$date&lt;/code&gt; object. So we avoided any unintended side effects which might catch us out later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immutable Dates in Carbon
&lt;/h3&gt;

&lt;p&gt;If you're using the Carbon library, you can use &lt;code&gt;Carbon\CarbonImmutable&lt;/code&gt; to create immutable date objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Carbon\CarbonImmutable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CarbonImmutable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$newDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addDay&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-01&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$newDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: 2025-01-02&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Converting Mutable Dates to Immutable Dates
&lt;/h2&gt;

&lt;p&gt;There may be times when you want to convert a mutable date object to an immutable one. You can do this by using the &lt;code&gt;DateTimeImmutable::createFromMutable&lt;/code&gt; method like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$mutableDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2025-01-01'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$immutableDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DateTimeImmutable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;createFromMutable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mutableDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Now $immutableDate is a DateTimeImmutable object&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Likewise, in Carbon, you can convert a mutable &lt;code&gt;Carbon\Carbon&lt;/code&gt; object to an immutable &lt;code&gt;Carbon\CarbonImmutable&lt;/code&gt; object using the &lt;code&gt;-&amp;gt;toImmutable()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Carbon\Carbon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$mutableDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Carbon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$immutableDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$mutableDate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toImmutable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Now $immutableDate is a CarbonImmutable object&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using Immutable Dates by Default in Laravel
&lt;/h2&gt;

&lt;p&gt;By default, when you try to get the current date using Laravel's &lt;code&gt;now()&lt;/code&gt; helper, it will return an instance of &lt;code&gt;Illuminate\Support\Carbon&lt;/code&gt;, which extends the mutable &lt;code&gt;Carbon\Carbon&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;However, you can change this behaviour so that &lt;code&gt;now()&lt;/code&gt; returns an instance of &lt;code&gt;Carbon\CarbonImmutable&lt;/code&gt; instead. This will allow you to use immutable dates by default within your Laravel application. To do this, you can use the &lt;code&gt;Date::use&lt;/code&gt; method in your &lt;code&gt;App\Providers\AppServiceProvider&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Providers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Carbon\CarbonImmutable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppServiceProvider&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ServiceProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Bootstrap any application services.
     */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CarbonImmutable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code example above, we are telling Laravel to use &lt;code&gt;Carbon\CarbonImmutable&lt;/code&gt; for all date instances created using the &lt;code&gt;now()&lt;/code&gt; helper or any other date-related helpers provided by Laravel.&lt;/p&gt;

&lt;p&gt;It will also ensure that any date attributes on your Eloquent models (such as &lt;code&gt;created_at&lt;/code&gt;, &lt;code&gt;updated_at&lt;/code&gt;) are cast to immutable date objects by default, rather than mutable ones.&lt;/p&gt;

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

&lt;p&gt;In this article, we've explored the differences between mutable and immutable date objects in PHP and the Carbon library. We've seen how mutable date objects can lead to unintended side effects when modified, whereas immutable date objects provide a safer, more predictable approach.&lt;/p&gt;

&lt;p&gt;If you enjoyed reading this post, you might be interested in checking out my 220+ page ebook "&lt;a href="https://battle-ready-laravel.com" rel="noopener noreferrer"&gt;Battle Ready Laravel&lt;/a&gt;" which covers similar topics in more depth.&lt;/p&gt;

&lt;p&gt;Or, you might want to check out my other 440+ page ebook "&lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;Consuming APIs in Laravel&lt;/a&gt;" which teaches you how to use Laravel to consume APIs from other services.&lt;/p&gt;

&lt;p&gt;If you're interested in getting updated each time I publish a new post, feel free to &lt;a href="https://ashallendesign.co.uk/blog" rel="noopener noreferrer"&gt;sign up for my newsletter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Keep on building awesome stuff! 🚀&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Saloon vs Guzzle vs SDK vs Http Facade for Laravel API Integrations</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Tue, 18 Nov 2025 15:20:00 +0000</pubDate>
      <link>https://forem.com/ashallendesign/saloon-vs-guzzle-vs-sdk-vs-http-facade-for-laravel-api-integrations-2a0e</link>
      <guid>https://forem.com/ashallendesign/saloon-vs-guzzle-vs-sdk-vs-http-facade-for-laravel-api-integrations-2a0e</guid>
      <description>&lt;p&gt;&lt;em&gt;The following article is a short snippet from the "Building an API Integration Using Saloon" chapter of my book "&lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;Consuming APIs In Laravel&lt;/a&gt;".&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The book teaches you how to write powerful and robust code to build API integrations in your Laravel apps. It's packed with over **440+ pages&lt;/em&gt;* of &lt;strong&gt;actionable advice&lt;/strong&gt;, &lt;strong&gt;real-life code examples&lt;/strong&gt;, and &lt;strong&gt;tips and tricks&lt;/strong&gt;.*&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In the book, I teach you how to write code that can help you **sleep easy at night having confidence that your API integration works!&lt;/em&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Saloon?
&lt;/h2&gt;

&lt;p&gt;Saloon is a PHP library created and maintained by Sam Carré that allows you to easily consume APIs in your PHP applications.&lt;/p&gt;

&lt;p&gt;It uses an elegant, expressive, object-oriented approach that makes it easy to build API integrations without worrying about the underlying implementation. Saloon has many features that make it easy to build API integrations, such as authentication handling, concurrent requests, pagination, error handling, and caching.&lt;/p&gt;

&lt;p&gt;It ships with handy default settings that make it easy for you to get started. But it also allows you to customize these settings to suit your needs.&lt;/p&gt;

&lt;p&gt;A key benefit of Saloon is that it comes with &lt;a href="https://ashallendesign.co.uk/blog/testing-saloon-api-integrations" rel="noopener noreferrer"&gt;powerful tools you can use for writing tests for your API integrations&lt;/a&gt;. This makes it simpler to write tests that are easy to read and maintainable.&lt;/p&gt;

&lt;p&gt;Saloon is also an excellent choice for consuming APIs in your Laravel applications as it encourages you to abstract your code into classes, such as "connectors" and "requests", which we'll cover later in this chapter. By logically separating the code, it makes it much easier for you to maintain and extend your integrations in the future.&lt;/p&gt;

&lt;p&gt;This chapter will focus on using Saloon v3. Specifically, we'll be using Saloon's Laravel driver, which allows us to easily integrate Saloon into our Laravel application by providing features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ashallendesign.co.uk/blog/processes-and-artisan-commands-in-laravel" rel="noopener noreferrer"&gt;Artisan&lt;/a&gt; commands for creating new connectors and request classes&lt;/li&gt;
&lt;li&gt;Facade for mocking and recording requests for testing&lt;/li&gt;
&lt;li&gt;Access to Laravel's &lt;code&gt;HTTP&lt;/code&gt; sender rather than the default &lt;code&gt;Guzzle&lt;/code&gt; sender&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alternatives to Saloon
&lt;/h2&gt;

&lt;p&gt;If you're building an API integration for your Laravel application, you can take several approaches to making HTTP requests. Let's take a quick look at some of the alternatives to Saloon:&lt;/p&gt;

&lt;h3&gt;
  
  
  Guzzle
&lt;/h3&gt;

&lt;p&gt;Guzzle is a popular PHP HTTP client that you can use to make HTTP requests. It's a great choice for making API requests, especially as it ships in the default installation of Laravel, so it doesn't require you to install any additional dependencies.&lt;/p&gt;

&lt;p&gt;It is relatively straightforward to use and also has tools that you can use for mocking responses in your tests.&lt;/p&gt;

&lt;p&gt;The core Saloon package actually uses Guzzle under the hood to make HTTP requests.&lt;/p&gt;

&lt;p&gt;An example request using Guzzle may look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://api.github.com/user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'headers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'Authorization'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Bearer '&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.github.token'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// $statusCode is: 200&lt;/span&gt;

&lt;span class="nv"&gt;$contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content-type'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// $contentType is: "application/json; charset=utf-8"&lt;/span&gt;

&lt;span class="nv"&gt;$contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContents&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;associative&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;JSON_THROW_ON_ERROR&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// $contents is: {"login": "ash-jc-allen", ...}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Http Facade
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Http&lt;/code&gt; facade is a class that ships with Laravel that you can use to make HTTP requests. It's a wrapper around Guzzle that simplifies some of Guzzle's functionality and makes it easier to use.&lt;/p&gt;

&lt;p&gt;In my opinion, the &lt;code&gt;Http&lt;/code&gt; facade provides two main benefits over using Guzzle directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;Http&lt;/code&gt; facade includes many methods you can use for testing. It allows you to mock the responses for individual calls and inspect HTTP requests to ensure your application sends them with the correct data.&lt;/li&gt;
&lt;li&gt;All the requests made via the &lt;code&gt;Http&lt;/code&gt; facade can be logged in Laravel Telescope (a package that provides a dashboard for debugging and inspecting your Laravel application). This makes it easy to visually inspect your requests and responses and debug any issues you may have.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a side note, if you'd like to log your HTTP requests made by Saloon, you can install the &lt;code&gt;saloonphp/laravel-http-sender&lt;/code&gt; package and configure Saloon to use the &lt;code&gt;Http&lt;/code&gt; facade as the sender rather than Guzzle. We'll cover how to do this in more depth later in this chapter.&lt;/p&gt;

&lt;p&gt;Continuing with our GitHub example from earlier, an example request using the &lt;code&gt;Http&lt;/code&gt; facade may look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;withToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ghp_cK1OKarnyDda0B2nyA5eiClSE3PywM3l123'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://api.github.com/user'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// $statusCode is 200&lt;/span&gt;

&lt;span class="nv"&gt;$contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content-type'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// $contentType is: "application/json; charset=utf-8"&lt;/span&gt;

&lt;span class="nv"&gt;$contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// $contents is: {"login": "ash-jc-allen", ...}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, the &lt;code&gt;Http&lt;/code&gt; facade hides away some of Guzzle's functionality for us to keep the code understandable and easy to read. For example, instead of needing to call &lt;code&gt;getBody()-&amp;gt;getContents()&lt;/code&gt; to get the response body and then using &lt;code&gt;json_decode&lt;/code&gt;, we can simply call &lt;code&gt;json()&lt;/code&gt; to get the response body as an array.&lt;/p&gt;

&lt;h3&gt;
  
  
  cURL
&lt;/h3&gt;

&lt;p&gt;Another option that you can use to make HTTP requests is "Client for URL" (cURL). PHP supports &lt;code&gt;libcurl&lt;/code&gt;, a library you can use to make different types of requests, such as HTTP requests.&lt;/p&gt;

&lt;p&gt;In fact, Guzzle uses cURL under the hood to make HTTP requests by default because it's a powerful and flexible library that's been around for a long time and is typically available on servers. This means that when using Saloon, you're indirectly using cURL under the hood.&lt;/p&gt;

&lt;p&gt;However, although cURL is a powerful tool, it is a lot of work to use and can be verbose. For example, take our example GitHub request and write it using cURL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'https://api.github.com/user'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HTTPHEADER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'Authorization: Bearer '&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.github.token'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'User-Agent: My-Laravel-App'&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;curl_errno&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Curl error: '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;curl_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HTTP_CODE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// $statusCode is 200&lt;/span&gt;

    &lt;span class="nv"&gt;$contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_CONTENT_TYPE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// $contentType is: "application/json; charset=utf-8"&lt;/span&gt;

    &lt;span class="nv"&gt;$contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;JSON_THROW_ON_ERROR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// $contents is: {"login": "ash-jc-allen", ...}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that it's a lot more verbose than the Guzzle and &lt;code&gt;Http&lt;/code&gt; facade examples. This makes it harder to read, understand, and maintain. A general rule I like to follow when writing code is to make it read like an English sentence as much as possible. The more plain English is used, the easier it will be for new developers to understand the code, and the easier it will be for me to understand it in the future. I don't feel cURL helps achieve this goal.&lt;/p&gt;

&lt;p&gt;You're unlikely to encounter cURL requests in your Laravel applications, but if you're working on an older application with some legacy code, you may encounter it.&lt;/p&gt;

&lt;h3&gt;
  
  
  API SDK
&lt;/h3&gt;

&lt;p&gt;Sometimes, you must build an API integration for your Laravel application and find that the third-party service has a software development kit (SDK) available. In the context of PHP applications, an SDK is typically a Composer package you can download and install into your application that provides a wrapper around the API you're integrating.&lt;/p&gt;

&lt;p&gt;For instance, imagine you want to interact with the Stripe API to take payments from your web application's customers. To do this, you may want to install Stripe's official PHP SDK (&lt;code&gt;stripe/stripe-php&lt;/code&gt;). As a basic example, you could write the following code to create a new customer using the Stripe package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Stripe\StripeClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$stripe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StripeClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.stripe.secret'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nv"&gt;$customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$stripe&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'description'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'John Doe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'payment_method'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'pm_card_visa'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the SDK provides a convenient wrapper around the API, making it easy to interact with and removing the need to worry about the underlying HTTP requests and responses. This way, you can focus on the business logic of your application.&lt;/p&gt;

&lt;p&gt;Generally, SDKs are built and maintained by the third-party service companies themselves. They act as a way to encourage developers to integrate with their API and make it as easy as possible to do so — they're essentially a marketing tool. For this reason, they're typically well-maintained and kept up to date with the latest API changes. Thus, you don't need to worry about keeping up to date with the API changes yourself, as the SDK will do this for you. You'll have the added benefit of the SDK including tests to ensure it works as expected. So, you can write your own tests to ensure that the SDK's methods are being called correctly without needing to test the underlying HTTP requests.&lt;/p&gt;

&lt;p&gt;However, I strongly recommend inspecting an SDK before you start using it. As mentioned, these packages and libraries are a marketing tool for third-party services. As with any other aspect of business, building and maintaining them costs money. If the package isn't especially popular, it may not be well-maintained. If this is the case, you may find that the SDK is outdated and doesn't support the latest API changes. This could cause issues for your application and may mean that you must write your own wrapper around the API.&lt;/p&gt;

&lt;p&gt;To help decide whether you should use an SDK, here are several questions to ask when inspecting it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How many downloads does the package have?&lt;/strong&gt; - This isn't a perfect metric, but it can give you a good idea of a package's popularity. It's likely to be well-maintained and updated if it has many downloads. It can also indicate that more people have been using it, meaning there's an increased chance bugs have been spotted and fixed. It's likely not very popular and may not be maintained very much if it has very few downloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How many open issues does the package have?&lt;/strong&gt; - Check out the issues for the package and see what types of problems you're seeing. Many issues may be feature requests, which aren't necessarily alarming. The issues you want to look out for are things like "this package doesn't work with the latest version of the API" or "this package doesn't work with the latest version of PHP". These issues can indicate that the package isn't well-maintained.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How many open pull requests does the package have?&lt;/strong&gt; - If there are open pull requests to fix bugs or add new features, this indicates community interest. Note the frequency of recent merges and whether minor fixes have been ignored for long periods to gauge maintainer activity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Should I Use Saloon?
&lt;/h3&gt;

&lt;p&gt;When planning your API integration, you might ask, "Should I use Saloon, Guzzle, the &lt;code&gt;Http&lt;/code&gt; facade, cURL, or the API SDK?". That's a valid question you'll need to answer based on your project's needs.&lt;/p&gt;

&lt;p&gt;Let's start by saying it's doubtful that you'll want to use cURL directly. Although it's a very powerful library, and you'll be interacting with it regardless of the other options you choose, it's not very easy to use and can be quite verbose. It's also not very easy to test compared to the other approaches, making it difficult to write tests for your application. It's important to remember that we must ensure that tests are easy to write. The easier they are to write, the more motivation you'll feel to write them. If tests are difficult to write, you'll be less likely to write them, which means you'll have less confidence in your application.&lt;/p&gt;

&lt;p&gt;You may want to use Guzzle or the &lt;code&gt;Http&lt;/code&gt; facade to build your integration. These are both perfectly valid approaches and ones I've used many times. They both have testing utilities and come packed with many convenient methods to simplify sending requests and reading responses. Of these two approaches, I would lean more towards the &lt;code&gt;Http&lt;/code&gt; facade as it makes the code look cleaner and more readable — and readability is very important. If you're only going to send a single API request in your application, you may want to use one of these rather than installing Saloon. Sam Carré, the creator of Saloon, has stated in the past that it may be easier to use one of these approaches rather than installing a new dependency (Saloon) and keeping it up to date in your application.&lt;/p&gt;

&lt;p&gt;If the third-party service you're using offers an SDK for interacting with their API, you may want to opt for that. This is, of course, assuming that it's well-maintained and that you're happy with the quality of the code. Using their package, you can take advantage of the code they've written. This will also reduce the burden of updating your code with the latest API changes.&lt;/p&gt;

&lt;p&gt;If there isn't a well-maintained SDK available, you can use the Saloon package. Saloon allows you to use an object-oriented approach to building your integration code with small, focused classes that are highly testable. You'll also have access to Saloon features such as caching responses, error handling, OAuth handling, plugins, and more. You can take advantage of all these features to build very extensive integrations.&lt;/p&gt;

&lt;p&gt;Another benefit of Saloon is its popularity and the fact that it's well maintained. As mentioned, it's important to ask questions before installing a new dependency in your application. You want to make sure that it's still actively being maintained and that it will be around for a long time. At the time of writing, Saloon boasts over 460k downloads (averaging at around 60k downloads per month), 1.6k stars on GitHub, and has had over 80 releases. The package also has a good-quality test suite with a high level of code coverage. These are all good indicators that the package is well maintained and is safe to use in your application.&lt;/p&gt;

&lt;p&gt;As a side note, you can also use Saloon to build your own API SDK. You may want to do this if you're building an API service and want other developers to start using it.&lt;/p&gt;

&lt;p&gt;No matter which of these approaches you take, I recommend writing a thin abstraction layer over your code. You can do this using &lt;a href="https://ashallendesign.co.uk/blog/using-interfaces-to-write-better-php-code" rel="noopener noreferrer"&gt;interfaces&lt;/a&gt; and dependency injection, as we've already covered in the "Code Techniques" section. Doing this can make it easier to switch out your implementation in the future. For example, imagine you have written your API integration using an SDK that is now abandoned and won't receive any more updates. You may want to use Saloon to rewrite the API calls and logic in this situation. If the code is abstracted away behind interfaces, you'll be able to swap out the implementation without needing to change any of the code that uses the API integration. This is a very powerful technique and one that I recommend using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to Continue Reading?
&lt;/h2&gt;

&lt;p&gt;Hopefully, this snippet from the book has given you a good overview of Saloon and how it compares to other approaches for building API integrations in your Laravel applications.&lt;/p&gt;

&lt;p&gt;If you've enjoyed it, you might be interested in &lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;reading the rest of the book&lt;/a&gt; which goes a lot more in depth about APIs and Saloon.&lt;/p&gt;

&lt;p&gt;The book teaches you how to write powerful and robust code to build API integrations in your Laravel apps. It's packed with over &lt;strong&gt;440+ pages&lt;/strong&gt; of &lt;strong&gt;actionable advice&lt;/strong&gt;, &lt;strong&gt;real-life code examples&lt;/strong&gt;, and &lt;strong&gt;tips and tricks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the book, I teach you how to write code that can help you &lt;strong&gt;sleep easy at night having confidence that your API integration works!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 Make sure to use the discount code &lt;strong&gt;ASHALLEN20&lt;/strong&gt; to get 20% off!&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://consuming-apis-in-laravel.com" rel="noopener noreferrer"&gt;Continue Reading&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Email Utilities for Laravel v1.0 Released! 🎉</title>
      <dc:creator>Ash Allen</dc:creator>
      <pubDate>Mon, 17 Nov 2025 16:33:15 +0000</pubDate>
      <link>https://forem.com/ashallendesign/email-utilities-for-laravel-v10-released-2e31</link>
      <guid>https://forem.com/ashallendesign/email-utilities-for-laravel-v10-released-2e31</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;A common feature I often need to build for public-facing forms is to prevent users from submitting forms with email addresses from disposable providers. These services provide temporary email addresses that can be used for a short time before being discarded.&lt;/p&gt;

&lt;p&gt;For example, when somebody signs up for my newsletter, they receive a free copy of my "Clean Coder's Guide to Laravel" PDF. But I found that some people were using disposable email addresses to sign up, download the PDF, and then discard the email address. This meant I then had a bunch of invalid email addresses in my mailing list, which isn't ideal.&lt;/p&gt;

&lt;p&gt;To solve this problem, in addition to using other techniques such as double opt-in and stronger email validation, I prevent users from signing up with disposable email addresses. I then found myself copying this same feature into multiple projects, so I decided to create a reusable package that I could use instead: Email Utilities for Laravel.&lt;/p&gt;

&lt;p&gt;In this article, I'll give an overview of what the package does and how to use it in your Laravel projects.&lt;/p&gt;

&lt;p&gt;If you're interested in checking out the package, you can find it on GitHub here: &lt;a href="https://github.com/ash-jc-allen/email-utilities" rel="noopener noreferrer"&gt;https://github.com/ash-jc-allen/email-utilities&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please note that, at the time of writing this article, this package does not validate whether an email address is valid or not. It is intended to be used in conjunction with Laravel's built-in &lt;code&gt;email&lt;/code&gt; validation rule. This package simply provides some additional utilities for working with email addresses that I have needed in multiple projects.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;To install the package, you'll need to be using at least PHP 8.3 and Laravel 11.0.&lt;/p&gt;

&lt;p&gt;You can install the package via Composer by running the following command in your project's root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require ashallendesign/email-utilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing the package, you can then publish the configuration file using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan php artisan vendor:publish --tag=email-utilities-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this command will create a &lt;code&gt;config/email-utilities.php&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Email" Class
&lt;/h2&gt;

&lt;p&gt;The package provides an &lt;code&gt;AshAllenDesign\EmailUtilities\Email&lt;/code&gt; class for interacting with email addresses.&lt;/p&gt;

&lt;p&gt;You can create a new instance of it by passing an email address to the constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;AshAllenDesign\EmailUtilities\Email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Disposable Email Addresses
&lt;/h3&gt;

&lt;p&gt;You can check whether a given email address is deemed to be disposable/temporary (meaning it's provided by a disposable email address provider) by using the &lt;code&gt;isDisposable()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;AshAllenDesign\EmailUtilities\Email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@0-mail.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isDisposable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@laravel.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isDisposable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package's list of disposable domains is defined in the &lt;code&gt;AshAllenDesign\EmailUtilities\Lists\DisposableDomainList&lt;/code&gt; class. You can output a list of all the disposable email address domains by using the &lt;code&gt;get()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use AshAllenDesign\EmailUtilities\Lists\DisposableDomainList;

$disposableEmailDomains = DisposableEmailDomains::get();

// [
    // '0-mail.com',
    // '027168.com',
    // '062e.com',
    // ...
// ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list of disposable email address providers is sourced from &lt;a href="https://github.com/disposable-email-domains/disposable-email-domains" rel="noopener noreferrer"&gt;https://github.com/disposable-email-domains/disposable-email-domains&lt;/a&gt;. It's worth remembering that new domains are being created all the time, so some disposable email addresses may not be detected. So please use this functionality with that in mind.&lt;/p&gt;

&lt;h3&gt;
  
  
  Role-based Email Addresses
&lt;/h3&gt;

&lt;p&gt;You may want to check whether a given email address is role-based. Role-based email addresses are those that are not specific to an individual, but rather to a role or function within an organisation. Examples include &lt;code&gt;admin@&lt;/code&gt;, &lt;code&gt;support@&lt;/code&gt;, &lt;code&gt;info@&lt;/code&gt; and &lt;code&gt;sales@&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To do this, you can use the &lt;code&gt;isRoleAccount()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;AshAllenDesign\EmailUtilities\Email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sales@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isRoleAccount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ash@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isRoleAccount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to the disposable email address domains, the package's list of role-based email address prefixes is defined in the &lt;code&gt;AshAllenDesign\EmailUtilities\Lists\RoleAccountList&lt;/code&gt; class. You can output a list of all the role-based email address prefixes by using the &lt;code&gt;get()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;AshAllenDesign\EmailUtilities\Lists\RoleAccountList&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$roleAccountList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RoleAccountList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// [&lt;/span&gt;
    &lt;span class="c1"&gt;// 'admin',&lt;/span&gt;
    &lt;span class="c1"&gt;// 'administrator',&lt;/span&gt;
    &lt;span class="c1"&gt;// 'contact',&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="c1"&gt;// ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please remember that this list is not exhaustive, so it may not detect all role-based email addresses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking the Domain of an Email Address
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "domainIs" Method
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;AshAllenDesign\EmailUtilities\Email&lt;/code&gt; class also provides a &lt;code&gt;domainIs&lt;/code&gt; method that checks whether the domain of an email address matches a given pattern. This is useful if you want to check whether an email address belongs to a specific domain or set of domains.&lt;/p&gt;

&lt;p&gt;The beauty of this method is that it supports wildcard (&lt;code&gt;*&lt;/code&gt;) patterns, so it allows for more flexible matching.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;AshAllenDesign\EmailUtilities\Email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'test.com'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'example*'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'ex*le.com'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'ex*le.com'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'example'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIs&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'test.com'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  "domainIsNot" Method
&lt;/h3&gt;

&lt;p&gt;Similarly, the &lt;code&gt;AshAllenDesign\EmailUtilities\Email&lt;/code&gt; class also provides a &lt;code&gt;domainIsNot&lt;/code&gt; method, which can be used to check whether the domain of an email address does not match a given pattern.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;AshAllenDesign\EmailUtilities\Email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIsNot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIsNot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'test.com'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIsNot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'example*'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIsNot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'ex*le.com'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIsNot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'ex*le.com'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIsNot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'example'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hello@example.com'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;domainIsNot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'test.com'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Validation Rules
&lt;/h2&gt;

&lt;p&gt;Please note that the validation rules included with this package don't validate that a value is actually an email address. These rules are intended to be used in conjunction with Laravel's built-in &lt;code&gt;email&lt;/code&gt; validation rule (&lt;a href="https://laravel.com/docs/12.x/validation#rule-email" rel="noopener noreferrer"&gt;https://laravel.com/docs/12.x/validation#rule-email&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  "EmailDomainIs" Rule
&lt;/h3&gt;

&lt;p&gt;The package provides an &lt;code&gt;AshAllenDesign\EmailUtilities\Rules\EmailDomainIs&lt;/code&gt; validation rule that can be used to validate that the domain of an email address matches a given pattern. This is useful if you want to ensure that an email address belongs to a specific domain or set of domains, such as only allowing email addresses from your own organisation.&lt;/p&gt;

&lt;p&gt;It uses the &lt;code&gt;AshAllenDesign\EmailUtilities\Email::domainIs&lt;/code&gt; method under the hood, so it supports wildcard (&lt;code&gt;*&lt;/code&gt;) patterns.&lt;/p&gt;

&lt;p&gt;You can use the rule like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;AshAllenDesign\EmailUtilities\Rules\EmailDomainIs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EmailDomainIs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*.example.com'&lt;/span&gt;&lt;span class="p"&gt;])],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this particular example, we've hardcoded the allowed domain pattern, but you may want to load this from a configuration file or the database instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  "EmailDomainIsNot" Rule
&lt;/h3&gt;

&lt;p&gt;Similar to the &lt;code&gt;EmailDomainIs&lt;/code&gt; rule, the package also provides an &lt;code&gt;AshAllenDesign\EmailUtilities\Rules\EmailDomainIsNot&lt;/code&gt; validation rule that can be used to validate that the domain of an email address does not match a given pattern. This is useful if you want to ensure that an email address does not belong to a specific domain, such as a list of known disposable email address providers.&lt;/p&gt;

&lt;p&gt;You can use the rule like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;AshAllenDesign\EmailUtilities\Rules\EmailDomainIsNot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EmailDomainIsNot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'disposable.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*.disposable.com'&lt;/span&gt;&lt;span class="p"&gt;])],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This validation rule also comes with a handy &lt;code&gt;disposable&lt;/code&gt; method so you can quickly add a rule to prevent disposable email addresses from being used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;AshAllenDesign\EmailUtilities\Rules\EmailDomainIsNot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;EmailDomainIsNot&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;disposable&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Config
&lt;/h2&gt;

&lt;p&gt;The package provides several options that can be configured via the published configuration file located at &lt;code&gt;config/email-utilities.php&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disposable Email Domains List
&lt;/h3&gt;

&lt;p&gt;By default, the package uses a built-in list of disposable email address domains defined in the &lt;code&gt;AshAllenDesign\EmailUtilities\Lists\DisposableDomainList&lt;/code&gt; class. Over time, this list may change as new disposable email address providers are created.&lt;/p&gt;

&lt;p&gt;However, you can maintain your own list of disposable domains by setting the &lt;code&gt;disposable_email_list_path&lt;/code&gt; configuration option like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'disposable_email_list_path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'./storage/app/disposable_email_domains.json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also publish the package's built-in list to your application by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan vendor:publish --tag=email-utilities-lists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a &lt;code&gt;disposable-domains.json&lt;/code&gt; file in your application's root directory. You can then modify this file as needed and update the &lt;code&gt;disposable_email_list_path&lt;/code&gt; configuration option to point to this file. Running this command will also publish a &lt;code&gt;role-accounts.json&lt;/code&gt; file that you can use to maintain your own list of role-based email address prefixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Role Accounts List
&lt;/h3&gt;

&lt;p&gt;Similar to the disposable email domains list, by default, the package uses a built-in list of role-based email address prefixes defined in the &lt;code&gt;AshAllenDesign\EmailUtilities\Lists\RoleAccountList&lt;/code&gt; class. However, you can maintain and provide your own list by setting the &lt;code&gt;role_account_list_path&lt;/code&gt; configuration option like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'role_accounts_list_path'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'./storage/app/role_account_list.json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Check Out the Package
&lt;/h2&gt;

&lt;p&gt;If you're interested in checking out the package, you can find it on GitHub here: &lt;a href="https://github.com/ash-jc-allen/email-utilities" rel="noopener noreferrer"&gt;https://github.com/ash-jc-allen/email-utilities&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
