<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://gregmayes.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://gregmayes.dev/" rel="alternate" type="text/html" /><updated>2026-07-05T16:24:49+00:00</updated><id>https://gregmayes.dev/feed.xml</id><title type="html">gregmayes.dev</title><author><name>Greg Mayes</name></author><entry><title type="html">Speaking at Laravel Manchester in August 2026</title><link href="https://gregmayes.dev/posts/2026/07/05/speaking-at-laravel-manchester-in-august-2026/" rel="alternate" type="text/html" title="Speaking at Laravel Manchester in August 2026" /><published>2026-07-05T00:00:00+00:00</published><updated>2026-07-05T00:00:00+00:00</updated><id>https://gregmayes.dev/posts/2026/07/05/speaking-at-laravel-manchester-in-august-2026</id><content type="html" xml:base="https://gregmayes.dev/posts/2026/07/05/speaking-at-laravel-manchester-in-august-2026/"><![CDATA[<p>After an extended break from speaking at tech events, I’m pleased to announce that I’ll be speaking at Laravel Manchester in August. It will be my first time at this particular meetup and I’ve heard nothing but good things about it.</p>

<p>The billed talk description: <br />
<em>Refactoring is often seen as a low-priority, time-intensive chore. Delegating refactors to an LLM is an option, but the PHP ecosystem offers some incredible tools that can automatically refactor your Laravel codebase with more confidence. Greg will showcase these tools and explain how you can get up and running with them</em></p>

<p>There’ll be plenty about Rector, PHPStan, and lessons I’ve learnt from trying to refactor codebases of varying ages and quality. If you’re interested in coming, you can RSVP <a href="https://www.meetup.com/meetup-group-mzuahgli/events/312846309">on Meetup</a>. There will also be a talk about mutation testing by Craig Duncan-Courcey.</p>]]></content><author><name>Greg Mayes</name></author><category term="posts" /><category term="Laravel" /><category term="PHP" /><category term="Public Speaking" /><summary type="html"><![CDATA[Announcing that I'll be speaking at Laravel Manchester in August 2026]]></summary></entry><entry><title type="html">Migrating a Jekyll Blog from jekyll-postcss to the Tailwind CLI</title><link href="https://gregmayes.dev/posts/2025/01/26/migrating-a-jekyll-blog-from-jekyll-postcss-to-tailwind-cli/" rel="alternate" type="text/html" title="Migrating a Jekyll Blog from jekyll-postcss to the Tailwind CLI" /><published>2025-01-26T00:00:00+00:00</published><updated>2025-01-26T00:00:00+00:00</updated><id>https://gregmayes.dev/posts/2025/01/26/migrating-a-jekyll-blog-from-jekyll-postcss-to-tailwind-cli</id><content type="html" xml:base="https://gregmayes.dev/posts/2025/01/26/migrating-a-jekyll-blog-from-jekyll-postcss-to-tailwind-cli/"><![CDATA[<h2 id="background">Background</h2>

<p>I don’t get opportunity to play around with front-end tooling much in my day-to-day work as of late. I try to use this blog as an excuse to better familiarise myself with Tailwind. I didn’t know much about Jekyll when I first set up this blog, so I followed the recommended approach for installing Tailwind with Jekyll and went down the PostCSS route. I used the <a href="https://github.com/mhanberg/jekyll-postcss">jekyll-postcss</a> Ruby gem to have it integrate seemlessly with Jekyll.</p>

<p>When I saw that <a href="https://tailwindcss.com/blog/tailwindcss-v4">Tailwind v4 had been released</a>, I figured I’d take the time to upgrade to it. At the time of writing, the <code class="language-plaintext highlighter-rouge">jekyll-postcss</code> gem hadn’t been updated for almost 4 years. I assumed that this wouldn’t be an issue because Tailwind v4 still supports PostCSS. As you might have guessed, I assumed wrong.</p>

<p>I bumbled along with the Tailwind v4 upgrade path. I ran the <a href="https://tailwindcss.com/docs/upgrade-guide#using-the-upgrade-tool">Tailwind upgrade tool</a> (which is awesome) and it did 90% of the work, but it wouldn’t touch my PostCSS configuration file as it contained “dynamic JavaScript”. The upgrade guide mentions changing your postcss.config.mjs to be something like the below:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
  <span class="na">plugins</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">@tailwindcss/postcss</span><span class="dl">"</span><span class="p">:</span> <span class="p">{},</span>
  <span class="p">}</span>
<span class="p">};</span></code></pre></figure>

<p>There’s just one problem there, my PostCSS configuration was a .js file with a CommonJS-style export statement. Before entertaining the idea of converting the file to be an .mjs file, I tried replacing <code class="language-plaintext highlighter-rouge">require('tailwindcss')</code> with <code class="language-plaintext highlighter-rouge">require('@tailwindcss/postcss')</code> in my postcss.config.js file, but I received the following error:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">PostCSS Error!

Error: Can<span class="s1">'t resolve '</span>tailwindcss<span class="s1">' in '</span>/home/greg/Projects<span class="s1">'
...
details: "resolve '</span>tailwindcss<span class="s1">' in '</span>/home/greg/Project Parsed request is a module<span class="s2">"</span></code></pre></figure>

<p>The problem was likely that I was trying to require an ECMAScript module inside a CommonJS-style configuration file. I probably should have investigated it more, but I simply couldn’t be bothered. I then took the logical step of trying to convert the config file to be an .mjs file but immediately ran into another issue. The <code class="language-plaintext highlighter-rouge">jekyll-postcss</code> gem only supports a postcss.config.js file and it doesn’t allow for any kind of filename override.</p>

<p>I have an extremely low tolerance to productivity blockers. I want to be able to keep my tools on the latest versions with as little friction as possible. I don’t want to clear this hurdle and then have similar problems when the next major Tailwind version is released. Dependencies are great until they’re not. I made the decision to rip everything PostCSS out of my blog and replace it with the first-party Tailwind CLI. In Tailwind v3, the CLI tool is installed by default, but in v4 you have to explicitly install it.</p>

<h2 id="removing-postcss">Removing PostCSS</h2>

<p>Because I was using <code class="language-plaintext highlighter-rouge">jekyll-postcss</code>, when I ran <code class="language-plaintext highlighter-rouge">bundle exec jekyll serve</code> it would also start a PostCSS “server” that would react to file changes. It would be great to have this level of Jekyll integration with the Tailwind CLI, but for now, I’m content to settle for an npm command that starts a Tailwind CLI watcher.</p>

<p>I removed the <code class="language-plaintext highlighter-rouge">jekyll-postcss</code> gem from my Gemfile as well as any mentions of PostCSS from my _config.yml file. I also removed the <code class="language-plaintext highlighter-rouge">postcss</code>, <code class="language-plaintext highlighter-rouge">autoprefixer</code>, and <code class="language-plaintext highlighter-rouge">cssnano</code> packages from my package.json. The Tailwind CLI handles vendor prefixing and minification, so I didn’t need those packages anymore.</p>

<p>Jekyll recommends the use of an assets directory to signify which assets (CSS, JS, images, etc.) should be brought across into the build. <code class="language-plaintext highlighter-rouge">jekyll-postcss</code> instructs you to append an empty <a href="https://jekyllrb.com/docs/front-matter/">front matter</a> block at the top of your .css file(s) to denote that they should be processed by Jekyll. In my case, I had a assets/css/main.css file. I also had a syntax.css file which contained the styles for my code blocks, but I decided to fold it into main.css.</p>

<p>When using the Tailwind CLI, you don’t need the front matter in the raw or transpiled .css file because you don’t want it to be transformed by Jekyll, only moved over as is. I made a _src/css/ directory and popped my main.css file in there. I set the whole of the _src directory to be excluded by Jekyll, which means it won’t end up in the built version. I also made sure to add assets/css/main.css to my .gitignore file as it will always be a build process artifact.</p>

<p>I created the below npm commands to make my life easier for both development and production. I thought about making a <code class="language-plaintext highlighter-rouge">dev</code> command that would run <code class="language-plaintext highlighter-rouge">jekyll:dev</code> and <code class="language-plaintext highlighter-rouge">tailwind:dev</code> concurrently, but it’s not exactly arduous to open two terminal panes.</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"jekyll:dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bundle exec jekyll serve"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"tailwind:dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx tailwindcss -i ./_src/css/main.css -o ./assets/css/main.css --watch"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"tailwind:prod"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx tailwindcss -i ./_src/css/main.css -o ./assets/css/main.css --minify"</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<h2 id="aftermath">Aftermath</h2>

<p>Doing this migration meant that I was able to upgrade to Tailwind v4 via the migration tool, with only a couple of manual changes. I was able to drop 1 Ruby gem and 3 JavaScript packages. I did have to install the <code class="language-plaintext highlighter-rouge">@tailwindcss/tailwind-cli</code> package, but this feels like more of a technicality as it was previously included in Tailwind v3.</p>

<p>I don’t know how useful this blog post is, but maybe it’ll help other people who use Tailwind on their Jekyll blogs.</p>]]></content><author><name>Greg Mayes</name></author><category term="posts" /><category term="Jekyll" /><category term="CSS" /><category term="Tailwind" /><category term="PostCSS" /><summary type="html"><![CDATA[When attempting to upgrade this blog to Tailwind v4, I bumped into a couple of issues with PostCSS. This article chronicles how I removed PostCSS and migrated over to the Tailwind CLI]]></summary></entry><entry><title type="html">Using Laravel Model Factories in PHPUnit Data Providers</title><link href="https://gregmayes.dev/posts/2024/02/03/using-laravel-model-factories-in-phpunit-data-providers/" rel="alternate" type="text/html" title="Using Laravel Model Factories in PHPUnit Data Providers" /><published>2024-02-03T00:00:00+00:00</published><updated>2024-02-03T00:00:00+00:00</updated><id>https://gregmayes.dev/posts/2024/02/03/using-laravel-model-factories-in-phpunit-data-providers</id><content type="html" xml:base="https://gregmayes.dev/posts/2024/02/03/using-laravel-model-factories-in-phpunit-data-providers/"><![CDATA[<p>If you’ve ever tried to use a <a href="https://laravel.com/docs/10.x/eloquent-factories">Laravel model factory</a> inside a <a href="https://docs.phpunit.de/en/11.0/writing-tests-for-phpunit.html#data-providers">PHPUnit data provider</a>, then you were likely greeted with an error message resembling <code class="language-plaintext highlighter-rouge">"A facade root has not been set"</code>. After scratching your head for a few minutes, you probably (wisely) decided that life is too short and then promptly scrapped the whole approach. The issue that you encountered is an expected consequence of the lifecycle of PHPUnit tests. In PHPUnit, data providers are called before a test’s <code class="language-plaintext highlighter-rouge">setUp</code> method. If you look inside the <code class="language-plaintext highlighter-rouge">Illuminate\Foundation\Testing\TestCase</code> class, which is the abstract class that Laravel feature tests ultimately extend, you’ll see the following snippet:</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="k">protected</span> <span class="k">function</span> <span class="n">setUp</span><span class="p">():</span> <span class="kt">void</span>
<span class="p">{</span>
    <span class="k">static</span><span class="o">::</span><span class="nv">$latestResponse</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>

    <span class="nc">Facade</span><span class="o">::</span><span class="nf">clearResolvedInstances</span><span class="p">();</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">app</span><span class="p">)</span> <span class="p">{</span>
        <span class="nv">$this</span><span class="o">-&gt;</span><span class="nf">refreshApplication</span><span class="p">();</span>

        <span class="nc">ParallelTesting</span><span class="o">::</span><span class="nf">callSetUpTestCaseCallbacks</span><span class="p">(</span><span class="nv">$this</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nv">$this</span><span class="o">-&gt;</span><span class="nf">setUpTraits</span><span class="p">();</span>

    <span class="k">foreach</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="n">afterApplicationCreatedCallbacks</span> <span class="k">as</span> <span class="nv">$callback</span><span class="p">)</span> <span class="p">{</span>
        <span class="nv">$callback</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="nc">Model</span><span class="o">::</span><span class="nf">setEventDispatcher</span><span class="p">(</span><span class="nv">$this</span><span class="o">-&gt;</span><span class="n">app</span><span class="p">[</span><span class="s1">'events'</span><span class="p">]);</span>

    <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">setUpHasRun</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">setUp</code> method inside that class is responsible for booting the Laravel framework in tests via the <code class="language-plaintext highlighter-rouge">refreshApplication</code> method. As data providers are executing before that <code class="language-plaintext highlighter-rouge">setUp</code> method has been called, you’re not going to have access to Laravel functionality such as facades and model factories at that point. You might think that this is the end of the road for using model factories inside your data providers. Fear not, there’s a neat trick that you can use to get around this limitation. If you wrap the provider’s data set within a closure, this will cause the expression to be evaluated lazily.</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>

<span class="kn">namespace</span> <span class="nn">Tests\Feature</span><span class="p">;</span>

<span class="kn">use</span> <span class="nc">App\Models\User</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">PHPUnit\Framework\Attributes\DataProvider</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">PHPUnit\Framework\Attributes\Test</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Tests\TestCase</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Closure</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Generator</span><span class="p">;</span>

<span class="kd">class</span> <span class="nc">UserTest</span> <span class="kd">extends</span> <span class="nc">TestCase</span>
<span class="p">{</span>
    <span class="na">#[Test]</span>
    <span class="na">#[DataProvider('singularUserProvider')]</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">testing_a_single_user_model</span><span class="p">(</span><span class="kt">Closure</span> <span class="nv">$userClosure</span><span class="p">):</span> <span class="kt">void</span>
    <span class="p">{</span>
        <span class="nv">$user</span> <span class="o">=</span> <span class="nv">$userClosure</span><span class="p">();</span>

        <span class="nv">$this</span><span class="o">-&gt;</span><span class="nf">assertInstanceOf</span><span class="p">(</span><span class="nc">User</span><span class="o">::</span><span class="n">class</span><span class="p">,</span> <span class="nv">$user</span><span class="p">);</span>
        <span class="nv">$this</span><span class="o">-&gt;</span><span class="nf">assertSame</span><span class="p">(</span><span class="s1">'Greg Mayes'</span><span class="p">,</span> <span class="nv">$user</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="na">#[Test]</span>
    <span class="na">#[DataProvider('multipleUsersProvider')]</span>
    <span class="k">public</span> <span class="k">function</span> <span class="n">testing_multiple_user_models</span><span class="p">(</span><span class="kt">Closure</span> <span class="nv">$usersClosure</span><span class="p">):</span> <span class="kt">void</span>
    <span class="p">{</span>
        <span class="k">foreach</span> <span class="p">(</span><span class="nv">$usersClosure</span><span class="p">()</span> <span class="k">as</span> <span class="nv">$user</span><span class="p">)</span> <span class="p">{</span>
            <span class="nv">$this</span><span class="o">-&gt;</span><span class="nf">assertInstanceOf</span><span class="p">(</span><span class="nc">User</span><span class="o">::</span><span class="n">class</span><span class="p">,</span> <span class="nv">$user</span><span class="p">);</span>
            <span class="nv">$this</span><span class="o">-&gt;</span><span class="nf">assertNotEmpty</span><span class="p">(</span><span class="nv">$user</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="n">singularUserProvider</span><span class="p">():</span> <span class="kt">Generator</span>
    <span class="p">{</span>
        <span class="k">yield</span> <span class="p">[</span><span class="k">static</span> <span class="k">fn</span> <span class="p">():</span> <span class="kt">User</span> <span class="o">=&gt;</span> <span class="nc">User</span><span class="o">::</span><span class="nf">factory</span><span class="p">()</span><span class="o">-&gt;</span><span class="nf">make</span><span class="p">([</span><span class="s1">'name'</span> <span class="o">=&gt;</span> <span class="s1">'Greg Mayes'</span><span class="p">])];</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="n">multipleUsersProvider</span><span class="p">():</span> <span class="kt">Generator</span>
    <span class="p">{</span>
        <span class="k">yield</span> <span class="p">[</span>
            <span class="k">static</span> <span class="k">fn</span> <span class="p">():</span> <span class="kt">array</span> <span class="o">=&gt;</span> <span class="p">[</span>
                <span class="nc">User</span><span class="o">::</span><span class="nf">factory</span><span class="p">()</span><span class="o">-&gt;</span><span class="nf">make</span><span class="p">([</span><span class="s1">'name'</span> <span class="o">=&gt;</span> <span class="s1">'Greg Mayes'</span><span class="p">]),</span>
                <span class="nc">User</span><span class="o">::</span><span class="nf">factory</span><span class="p">()</span><span class="o">-&gt;</span><span class="nf">make</span><span class="p">([</span><span class="s1">'name'</span> <span class="o">=&gt;</span> <span class="s1">'Joe Bloggs'</span><span class="p">]),</span>
            <span class="p">]</span>
        <span class="p">];</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>The above approach does have the downside of requiring you to invoke the <code class="language-plaintext highlighter-rouge">Closure</code> inside the body of your test, but it’s a relatively small price to pay. For the sake of completeness, I’ve included examples of returning a singular <code class="language-plaintext highlighter-rouge">User</code> model in a data set as well as returning an array of <code class="language-plaintext highlighter-rouge">User</code> models.</p>

<p>If you want to learn more about PHPUnit data providers outside of the previously linked documentation, then I would highly recommend <a href="https://barlow.dev/posts/phpunits-data-providers-are-your-friend">this blog post</a> by Matt Barlow.</p>]]></content><author><name>Greg Mayes</name></author><category term="posts" /><category term="PHP" /><category term="PHPUnit" /><category term="Laravel" /><summary type="html"><![CDATA[Using Laravel model factories inside PHPUnit data providers isn't as straightforward as you would think. This article shows you how can pull this off.]]></summary></entry><entry><title type="html">Importing Global Functions in Namespaced PHP Code</title><link href="https://gregmayes.dev/posts/2023/12/17/php-importing-global-functions/" rel="alternate" type="text/html" title="Importing Global Functions in Namespaced PHP Code" /><published>2023-12-17T00:00:00+00:00</published><updated>2023-12-17T00:00:00+00:00</updated><id>https://gregmayes.dev/posts/2023/12/17/php-importing-global-functions</id><content type="html" xml:base="https://gregmayes.dev/posts/2023/12/17/php-importing-global-functions/"><![CDATA[<p>You may have seen namespaced PHP code where global functions have been imported via <code class="language-plaintext highlighter-rouge">use function</code> or have been fully-qualified using a <code class="language-plaintext highlighter-rouge">\</code> character. Or perhaps you use a static analysis tool that routinely bugs you to import global functions and you don’t really understand why. In this short article, I’m going to explain the “why” behind this particular micro-optimisation.</p>

<p>I’ve used the <code class="language-plaintext highlighter-rouge">str_repeat</code> function in the example below but it could have been another internal PHP function or a globally scoped function declared in something like a <code class="language-plaintext highlighter-rouge">helpers.php</code> file.</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>

<span class="kn">namespace</span> <span class="nn">Foo\Bar\Baz</span><span class="p">;</span>

<span class="kn">use</span> <span class="k">function</span> <span class="nf">str_repeat</span><span class="p">;</span>

<span class="k">echo</span> <span class="nb">str_repeat</span><span class="p">(</span><span class="s1">'teststring'</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span></code></pre></figure>

<p>I’m going to run the code snippet through an Opcode dumper called <a href="https://pecl.php.net/package/vld">Vulcan Logic Dumper (VLD)</a>. If you’d like to play around with VLD without installing it on your local environment, you can easily run it using <a href="https://3v4l.org">3v4l.org</a>. After you <code class="language-plaintext highlighter-rouge">eval();</code> your code, you should see a tab called ‘VLD’ at the top of the results box. You can also look at the performance metrics of the executed code by using the ‘Performance’ tab.</p>

<p>In the Opcode dump below, we can see that on line 0 that there’s an <code class="language-plaintext highlighter-rouge">INIT_FCALL</code> Opcode for the invocation of <code class="language-plaintext highlighter-rouge">str_repeat</code>. On lines 1 and 2, there are <code class="language-plaintext highlighter-rouge">SEND_VAL</code> Opcodes on for passing the arguments to the function.</p>

<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">number of ops:  6
compiled vars:  none
line      #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    7     0  E &gt;   INIT_FCALL                                               'str_repeat'
          1        SEND_VAL                                                 'teststring'
          2        SEND_VAL                                                 3
          3        DO_ICALL                                         $0      
          4        ECHO                                                     $0
          5      &gt; RETURN                                                   1</code></pre></figure>

<p>You might think that this is how it always looks when you call a function such as <code class="language-plaintext highlighter-rouge">str_repeat</code> within namespaced code. However, that <code class="language-plaintext highlighter-rouge">INIT_FCALL</code> Opcode is only being used here because of the explicit importing of  <code class="language-plaintext highlighter-rouge">str_repeat</code> function from the global namespace. This would also be the case if you removed the <code class="language-plaintext highlighter-rouge">use function</code> statement and called the function as <code class="language-plaintext highlighter-rouge">\str_repeat('teststring', 3)</code> instead.</p>

<p>If I run that snippet again without the import, I get slightly different results this time. The Opcode on line 0 in the below dump is now <code class="language-plaintext highlighter-rouge">INIT_NS_FCALL_BY_NAME</code> rather than <code class="language-plaintext highlighter-rouge">INIT_FCALL</code>. This means that PHP is looking for the function <code class="language-plaintext highlighter-rouge">str_repeat</code> inside the namespace <code class="language-plaintext highlighter-rouge">Foo\Bar\Baz</code>, which we know doesn’t exist. It will then traverse the namespace tree (<code class="language-plaintext highlighter-rouge">Foo\Bar</code>, <code class="language-plaintext highlighter-rouge">Foo</code>) until it hits the global namespace and finds the internal PHP function <code class="language-plaintext highlighter-rouge">str_repeat</code>. The performance implications of this are hard to quantify and will vary greatly depending on your application, but I think we can all agree that fewer operations to produce the same result is preferable.</p>

<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">number of ops:  6
compiled vars:  none
line      #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    5     0  E &gt;   INIT_NS_FCALL_BY_NAME                                    'Foo%5CBar%5CBaz%5Cstr_repeat'
          1        SEND_VAL_EX                                              'teststring'
          2        SEND_VAL_EX                                              3
          3        DO_FCALL                                      0  $0      
          4        ECHO                                                     $0
          5      &gt; RETURN                                                   1</code></pre></figure>

<p>Avoiding namespace traversal is just one reason to import global functions within namespaced code. I’ll explore the other reason by calling the <code class="language-plaintext highlighter-rouge">strlen</code> function with an explicit import; I’ve made a crude example function called <code class="language-plaintext highlighter-rouge">isGreaterThanFiveCharacters</code> that returns whether an ASCII string is greater than 5 characters or not.</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>

<span class="kn">namespace</span> <span class="nn">Foo\Bar\Baz</span><span class="p">;</span>

<span class="kn">use</span> <span class="k">function</span> <span class="n">strlen</span><span class="p">;</span>

<span class="k">function</span> <span class="n">isGreaterThanFiveCharacters</span><span class="p">(</span><span class="kt">string</span> <span class="nv">$input</span><span class="p">):</span> <span class="kt">bool</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="nb">strlen</span><span class="p">(</span><span class="nv">$input</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">5</span><span class="p">;</span>
<span class="p">}</span>

<span class="nf">isGreaterThanFiveCharacters</span><span class="p">(</span><span class="s1">'test string'</span><span class="p">);</span></code></pre></figure>

<p>On line 1 of the below Opcode dump, the Opcode is <code class="language-plaintext highlighter-rouge">STRLEN</code> rather than <code class="language-plaintext highlighter-rouge">INIT_FCALL</code>. This is because <code class="language-plaintext highlighter-rouge">strlen</code> is one of a limited number of internal PHP functions that have their own Opcodes. The <code class="language-plaintext highlighter-rouge">STRLEN</code> Opcode means that rather than the Zend Engine having to call a function to calculate the length of the string, it can instead access the <code class="language-plaintext highlighter-rouge">len</code> property from the <a href="https://www.phpinternalsbook.com/php7/internal_types/strings/zend_strings.html">zend_string</a> struct. To my knowledge there isn’t an official list of the internal functions that have an accompanying Opcode, but <a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/src/Fixer/FunctionNotation/NativeFunctionInvocationFixer.php#L327">this method from PHP-CS-Fixer</a> looks to cover them all.</p>

<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">function name:  Foo\Bar\Baz\isGreaterThanFiveCharacters
number of ops:  7
compiled vars:  !0 = $input
line      #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    7     0  E &gt;   RECV                                             !0      
    9     1        STRLEN                                           ~1      !0
          2        IS_SMALLER                                       ~2      5, ~1
          3        VERIFY_RETURN_TYPE                                       ~2
          4      &gt; RETURN                                                   ~2
   10     5*       VERIFY_RETURN_TYPE                                       
          6*     &gt; RETURN                                                   null</code></pre></figure>

<p>What if I was to run the <code class="language-plaintext highlighter-rouge">isGreaterThanFiveCharacters</code> function again, but this time without importing the <code class="language-plaintext highlighter-rouge">strlen</code> function?</p>

<figure class="highlight"><pre><code class="language-plaintext" data-lang="plaintext">function name:  Foo\Bar\Baz\isGreaterThanFiveCharacters
number of ops:  9
compiled vars:  !0 = $input
line      #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    5     0  E &gt;   RECV                                             !0      
    7     1        INIT_NS_FCALL_BY_NAME                                    'Foo%5CBar%5CBaz%5Cstrlen'
          2        SEND_VAR_EX                                              !0
          3        DO_FCALL                                      0  $1      
          4        IS_SMALLER                                       ~2      5, $1
          5        VERIFY_RETURN_TYPE                                       ~2
          6      &gt; RETURN                                                   ~2
    8     7*       VERIFY_RETURN_TYPE                                       
          8*     &gt; RETURN                                                   null</code></pre></figure>

<p>I now get the <code class="language-plaintext highlighter-rouge">INIT_NS_FCALL_BY_NAME</code> Opcode instead of <code class="language-plaintext highlighter-rouge">STRLEN</code>, followed by <code class="language-plaintext highlighter-rouge">SEND_VAR_EX</code> and <code class="language-plaintext highlighter-rouge">DO_FCALL</code>. Huh, what’s going on here? Well, it turns out that internal PHP functions with accompanying Opcodes are only able to utilise them inside a namespace if the function has been imported or has been fully-qualified. This mechanic doesn’t apply to non-namespaced code, where the aforementioned functions will always use their accompanying Opcodes.</p>

<p>If you were to call that <code class="language-plaintext highlighter-rouge">isGreaterThanFiveCharacters</code> function hundreds of thousands of times in a performance sensitive loop inside namespaced code without having imported <code class="language-plaintext highlighter-rouge">strlen</code>, you’re going to suffer two performance penalties. The first being that your application is going to spend time needlessly traversing namespaces to find <code class="language-plaintext highlighter-rouge">strlen</code>. The second is that PHP will have to invoke the <code class="language-plaintext highlighter-rouge">strlen</code> function each time, when it could get the <code class="language-plaintext highlighter-rouge">len</code> property from the <code class="language-plaintext highlighter-rouge">zend_string</code> struct instead, and skip the function call entirely.</p>]]></content><author><name>Greg Mayes</name></author><category term="posts" /><category term="PHP" /><category term="Performance" /><summary type="html"><![CDATA[Importing global functions in namespaced PHP code can improve performance by a surprising amount. This article explores how and why it works.]]></summary></entry></feed>