<?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>DEV Community: Florian Engelhardt</title>
    <description>The latest articles on DEV Community by Florian Engelhardt (@realflowcontrol).</description>
    <link>https://dev.to/realflowcontrol</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%2F164512%2F44135fcf-7705-446a-b267-3a01b0b562ba.jpg</url>
      <title>DEV Community: Florian Engelhardt</title>
      <link>https://dev.to/realflowcontrol</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/realflowcontrol"/>
    <language>en</language>
    <item>
      <title>To double quote or not, that's the question!</title>
      <dc:creator>Florian Engelhardt</dc:creator>
      <pubDate>Fri, 16 Aug 2024 07:54:09 +0000</pubDate>
      <link>https://dev.to/realflowcontrol/too-double-quote-or-not-thats-the-question-78l</link>
      <guid>https://dev.to/realflowcontrol/too-double-quote-or-not-thats-the-question-78l</guid>
      <description>&lt;p&gt;Just recently I heard again that PHP folks still talk about single quotes vs. double quotes and that using single quotes is just a micro optimisation but if you get used to using single quotes all the time you'd save a bunch of CPU cycles!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Everything has already been said, but not yet by everyone" – &lt;a href="https://en.wikipedia.org/wiki/Karl_Valentin" rel="noopener noreferrer"&gt;Karl Valentin&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is in this spirit that I am writing an article about the same topic Nikita Popov did already 12 years ago (if you are reading &lt;a href="https://www.npopov.com/2012/01/09/Disproving-the-Single-Quotes-Performance-Myth.html" rel="noopener noreferrer"&gt;his article&lt;/a&gt;, you can stop reading here).&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the fuzz all about?
&lt;/h2&gt;

&lt;p&gt;PHP performs &lt;a href="https://www.php.net/manual/en/language.types.string.php#language.types.string.parsing" rel="noopener noreferrer"&gt;string interpolation&lt;/a&gt;, in which it searches for the use of variables in a string and replaces them with the value of the variable 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="nv"&gt;$juice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apple"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"They drank some &lt;/span&gt;&lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="s2"&gt; juice."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// will output: They drank some apple juice.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This feature is limited to strings in double quotes and &lt;a href="https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc" rel="noopener noreferrer"&gt;heredoc&lt;/a&gt;. Using single quotes (or &lt;a href="https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.nowdoc" rel="noopener noreferrer"&gt;nowdoc&lt;/a&gt;) will yield a different result:&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;$juice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apple"&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;'They drank some $juice juice.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// will output: They drank some $juice juice.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at that: PHP will not search for variables in that single quoted string. So we could just start using single quotes everywhere. So people started suggesting changes like this ..&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- $juice = "apple";
&lt;/span&gt;&lt;span class="gi"&gt;+ $juice = 'apple';
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;.. because it'll be faster and it'd save a bunch of CPU cycles with every execution of that code because PHP does not look for variables in single quoted strings (which are non-existent in the example anyway) and everyone is happy, case closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case closed?
&lt;/h2&gt;

&lt;p&gt;Obviously there is a difference in using single quotes vs. double quotes, but in order to understand what is going on we need to dig a bit deeper.&lt;/p&gt;

&lt;p&gt;Even though PHP is an interpreted language it is using a compile step in which certain parts play together to get something the virtual machine can actually execute, which is opcodes. So how do we get from PHP source code to opcodes?&lt;/p&gt;

&lt;h3&gt;
  
  
  The lexer
&lt;/h3&gt;

&lt;p&gt;The lexer scans the source code file and breaks it down into tokens. A simple example of what this means can be found in the &lt;a href="https://www.php.net/manual/en/function.token-get-all.php" rel="noopener noreferrer"&gt;&lt;code&gt;token_get_all()&lt;/code&gt; function documentation&lt;/a&gt;. A PHP source code of just &lt;code&gt;&amp;lt;?php echo "";&lt;/code&gt; becomes these tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T_OPEN_TAG (&amp;lt;?php )
T_ECHO (echo)
T_WHITESPACE ( )
T_CONSTANT_ENCAPSED_STRING ("")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see this in action and play with it &lt;a href="https://3v4l.org/h643R" rel="noopener noreferrer"&gt;in this 3v4l.org snippet&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The parser
&lt;/h3&gt;

&lt;p&gt;The parser takes these tokens and generates an abstract syntax tree from them. An AST representation of the above example looks like this when represented as a JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"nodeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Stmt_Echo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"startLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"startTokenPos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"startFilePos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"endLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"endTokenPos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"endFilePos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"exprs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"nodeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Scalar_String"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"startLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"startTokenPos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"startFilePos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"endLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"endTokenPos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"endFilePos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"rawValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In case you wanna play with this as well and see how the AST for other code looks like, I found &lt;a href="https://phpast.com/" rel="noopener noreferrer"&gt;https://phpast.com/&lt;/a&gt; by &lt;a href="https://ryangjchandler.co.uk/" rel="noopener noreferrer"&gt;Ryan Chandler&lt;/a&gt; and &lt;a href="https://php-ast-viewer.com/" rel="noopener noreferrer"&gt;https://php-ast-viewer.com/&lt;/a&gt; which both show you the AST of a given piece of PHP code.&lt;/p&gt;

&lt;h3&gt;
  
  
  The compiler
&lt;/h3&gt;

&lt;p&gt;The compiler takes the AST and creates opcodes. The opcodes are the things the virtual machine executes, it is also what will be stored in the OPcache if you have that setup and enabled (which I highly recommend).&lt;/p&gt;

&lt;p&gt;To view the opcodes we have multiple options (maybe more, but I do know these three):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;use the &lt;a href="https://github.com/derickr/vld" rel="noopener noreferrer"&gt;vulcan logic dumper&lt;/a&gt;  extension. It is also &lt;a href="https://3v4l.org/FuaYl/vld" rel="noopener noreferrer"&gt;baked into 3v4l.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;use &lt;code&gt;phpdbg -p script.php&lt;/code&gt; to dump the opcodes &lt;/li&gt;
&lt;li&gt;or use the &lt;code&gt;opcache.opt_debug_level&lt;/code&gt; INI setting for OPcache to make it print out the opcodes

&lt;ul&gt;
&lt;li&gt;a value of &lt;code&gt;0x10000&lt;/code&gt; outputs opcodes before optimisation&lt;/li&gt;
&lt;li&gt;a value of &lt;code&gt;0x20000&lt;/code&gt; outputs opcodes after optimisation
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;?php echo "";'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; foo.php
&lt;span class="nv"&gt;$ &lt;/span&gt;php &lt;span class="nt"&gt;-dopcache&lt;/span&gt;.opt_debug_level&lt;span class="o"&gt;=&lt;/span&gt;0x10000 foo.php
&lt;span class="nv"&gt;$_main&lt;/span&gt;:
...
0000 ECHO string&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
0001 RETURN int&lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hypothesis
&lt;/h2&gt;

&lt;p&gt;Coming back to the initial idea of saving CPU cycles when using single quotes vs. double quotes, I think we all agree that this would only be true if PHP would evaluate these strings at runtime for every single request.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens at runtime?
&lt;/h2&gt;

&lt;p&gt;So let's see which opcodes PHP creates for the two different versions.&lt;/p&gt;

&lt;p&gt;Double quotes:&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="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"apple"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000 ECHO string("apple")
0001 RETURN int(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vs. single quotes:&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="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'apple'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000 ECHO string("apple")
0001 RETURN int(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hey wait, something weird happened. This looks identical! Where did my micro optimisation go?&lt;/p&gt;

&lt;p&gt;Well maybe, just maybe the &lt;code&gt;ECHO&lt;/code&gt; opcode handler's implementation parses the given string, although there is no marker or something else which tells it to do so ... hmm 🤔&lt;/p&gt;

&lt;p&gt;Let's try a different approach and see what the lexer does for those two cases:&lt;/p&gt;

&lt;p&gt;Double quotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T_OPEN_TAG (&amp;lt;?php )
T_ECHO (echo)
T_WHITESPACE ( )
T_CONSTANT_ENCAPSED_STRING ("")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vs. single quotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T_OPEN_TAG (&amp;lt;?php )
T_ECHO (echo)
T_WHITESPACE ( )
T_CONSTANT_ENCAPSED_STRING ('')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tokens are still distinguishing between double and single quotes, but checking the AST will give us an identical result for both cases - the only difference is the &lt;code&gt;rawValue&lt;/code&gt; in the &lt;code&gt;Scalar_String&lt;/code&gt; node attributes, that still has the single/double quotes, but the &lt;code&gt;value&lt;/code&gt; uses double quotes in both cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Hypothesis
&lt;/h2&gt;

&lt;p&gt;Could it be, that string interpolation is actually done at compile time?&lt;/p&gt;

&lt;p&gt;Let's check with a slightly more "sophisticated" 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="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"apple"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"juice: &lt;/span&gt;&lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tokens for this file are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T_OPEN_TAG (&amp;lt;?php)
T_VARIABLE ($juice)
T_CONSTANT_ENCAPSED_STRING ("apple")
T_WHITESPACE ()
T_ECHO (echo)
T_WHITESPACE ( )
T_ENCAPSED_AND_WHITESPACE (juice: )
T_VARIABLE ($juice)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the last two tokens! String interpolation is handled in the lexer and as such is a compile time thing and has nothing to do with runtime.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsq4s95ryinqywgi650f6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsq4s95ryinqywgi650f6.png" alt="BUSTED" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For completeness, let's have a look at the opcodes generated by this (after optimisation, using &lt;code&gt;0x20000&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000 ASSIGN CV0($juice) string("apple")
0001 T2 = FAST_CONCAT string("juice: ") CV0($juice)
0002 ECHO T2
0003 RETURN int(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is different opcode than we had in our simple &lt;code&gt;&amp;lt;?php echo "";&lt;/code&gt; example, but this is okay because we are doing something different here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get to the point: should I concat or interpolate?
&lt;/h2&gt;

&lt;p&gt;Let's have a look at these three different versions:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$juice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apple"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"juice: &lt;/span&gt;&lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"juice: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"juice: "&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;the first version is using string interpolation&lt;/li&gt;
&lt;li&gt;the second is using a comma separation (which AFAIK only works with &lt;code&gt;echo&lt;/code&gt; and not with assigning variables or anything else)&lt;/li&gt;
&lt;li&gt;and the third option uses string concatenation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first opcode assigns the string "apple" to the variable &lt;code&gt;$juice&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000 ASSIGN CV0($juice) string("apple")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first version (string interpolation) is using a &lt;a href="https://en.wikipedia.org/wiki/Rope_(data_structure)" rel="noopener noreferrer"&gt;rope&lt;/a&gt; as the underlying data structure, which is optimised to do as little string copies as possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0001 T2 = ROPE_INIT 4 string("juice: ")
0002 T2 = ROPE_ADD 1 T2 CV0($juice)
0003 T2 = ROPE_ADD 2 T2 string(" ")
0004 T1 = ROPE_END 3 T2 CV0($juice)
0005 ECHO T1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version is the most memory effective as it does not create an intermediate string representation. Instead it does multiple calls to &lt;code&gt;ECHO&lt;/code&gt; which is a blocking call from an I/O perspective so depending on your use case this might be a downside.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0006 ECHO string("juice: ")
0007 ECHO CV0($juice)
0008 ECHO string(" ")
0009 ECHO CV0($juice)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third version uses &lt;code&gt;CONCAT&lt;/code&gt;/&lt;code&gt;FAST_CONCAT&lt;/code&gt; to create an intermediate string representation and as such might do more memory copies and/or use more memory than the rope version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0010 T1 = CONCAT string("juice: ") CV0($juice)
0011 T2 = FAST_CONCAT T1 string(" ")
0012 T1 = CONCAT T2 CV0($juice)
0013 ECHO T1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So ... what is the right thing to do here and why is it string interpolation?&lt;/p&gt;

&lt;p&gt;String interpolation uses either a &lt;code&gt;FAST_CONCAT&lt;/code&gt; in the case of &lt;code&gt;echo "juice: $juice";&lt;/code&gt; or highly optimised &lt;code&gt;ROPE_*&lt;/code&gt; opcodes in the case of &lt;code&gt;echo "juice: $juice $juice";&lt;/code&gt;, but most important it communicates the intent clearly and none of this has been bottle neck in any of the PHP applications I have worked with so far, so none of this actually matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;String interpolation is a compile time thing. Granted, without OPcache the lexer will have to check for variables used in double quoted strings on every request, even if there aren't any, waisting CPU cycles, but honestly: The problem is not the double quoted strings, but not using OPcache!&lt;/p&gt;

&lt;p&gt;However, there is one caveat: PHP up to 4 (and I believe even including 5.0 and maybe even 5.1, I don't know) did string interpolation at runtime, so using these versions ... hmm, I guess if anyone really still uses PHP 5, the same as above applies: The problem is not the double quoted strings, but the use of an outdated PHP version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final advice
&lt;/h2&gt;

&lt;p&gt;Update to the latest PHP version, enable OPcache and live happily ever after!&lt;/p&gt;

&lt;p&gt;[Edit: August 16th]&lt;/p&gt;

&lt;h1&gt;
  
  
  What about &lt;code&gt;sprintf()&lt;/code&gt;?
&lt;/h1&gt;

&lt;p&gt;So actually I intended to say that none of this is a performance problem, if you are using string interpolation, single quotes and concatenation or anything else. Someone stepped up and mentioned &lt;code&gt;sprintf()&lt;/code&gt; and where this clocks in performance-wise. So for the sake of completeness, lets have a look at &lt;code&gt;sprintf()&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="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$juice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"apple"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"juice: %s %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$juice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;compiles to the following opcode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000 ASSIGN CV0($juice) string("apple")
0001 INIT_FCALL 3 128 string("sprintf")
0002 SEND_VAL string("juice: %s %s") 1
0003 SEND_VAR CV0($juice) 2
0004 SEND_VAR CV0($juice) 3
0005 V1 = DO_ICALL
0006 ECHO V1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick benchmark shows that the &lt;code&gt;sprintf()&lt;/code&gt; variant takes 14 to 21 times as long as the string interpolation variant on my local machine.&lt;/p&gt;

&lt;p&gt;Here comes the catch: this is only true up to PHP 8.3, PHP 8.4 comes with another &lt;a href="https://tideways.com/profiler/blog/new-in-php-8-4-engine-optimization-of-sprintf-to-string-interpolation" rel="noopener noreferrer"&gt;compile time optimisation&lt;/a&gt; that will treat &lt;code&gt;sprintf()&lt;/code&gt; calls that just have &lt;code&gt;%s&lt;/code&gt; and &lt;code&gt;%d&lt;/code&gt; in them as if you wrote string interpolation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0000 ASSIGN CV0($juice) string("apple")
0001 T2 = ROPE_INIT 4 string("juice: ")
0002 T2 = ROPE_ADD 1 T2 CV0($juice)
0003 T2 = ROPE_ADD 2 T2 string(" ")
0004 T1 = ROPE_END 3 T2 CV0($juice)
0005 ECHO T1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the final advice still holds: update to the latest PHP version (well, maybe wait with upgrading to PHP 8.4 until there is a stable release).&lt;/p&gt;

</description>
      <category>php</category>
      <category>performance</category>
    </item>
    <item>
      <title>Processing One Billion Rows in PHP!</title>
      <dc:creator>Florian Engelhardt</dc:creator>
      <pubDate>Fri, 08 Mar 2024 09:27:36 +0000</pubDate>
      <link>https://dev.to/realflowcontrol/processing-one-billion-rows-in-php-3eg0</link>
      <guid>https://dev.to/realflowcontrol/processing-one-billion-rows-in-php-3eg0</guid>
      <description>&lt;p&gt;You may have heard of the "The One Billion Row Challenge" (1brc) and in case you don't, go checkout &lt;a href="https://github.com/gunnarmorling/1brc" rel="noopener noreferrer"&gt;Gunnar Morlings's 1brc repo&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I got sucked in because two of my colleagues have entered the competition and are on the &lt;a href="https://github.com/gunnarmorling/1brc?tab=readme-ov-file#results" rel="noopener noreferrer"&gt;leaderboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;PHP is not known for its speed, but as I am working on the PHP profiler I thought I give it a shot and see how fast it can get.&lt;/p&gt;

&lt;h2&gt;
  
  
  A first naive approach
&lt;/h2&gt;

&lt;p&gt;I cloned the repository and created the billion row dataset in &lt;code&gt;measurements.txt&lt;/code&gt;. After that I started building my first naive implementation of something that could solve the challenge:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nv"&gt;$stations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="nv"&gt;$fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'measurements.txt'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fgetcsv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&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;if&lt;/span&gt; &lt;span class="p"&gt;(&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;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$data&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="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$data&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nv"&gt;$data&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;$data&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;$data&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="mi"&gt;1&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;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$data&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$data&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$data&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&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="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$data&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$data&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&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="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;$data&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$data&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$data&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&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="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;ksort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stations&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;'{'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stations&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$k&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$station&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$station&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$station&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$station&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&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;$k&lt;/span&gt;&lt;span class="p"&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;$station&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="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$station&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&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;$station&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="s1"&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="s1"&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 is nothing wild in here, just open the file, use &lt;code&gt;fgetcsv()&lt;/code&gt; to read the data. If the station is not found already, create it, otherwise increment the counter, sum the temperature and see if the current temperature is lower than or higher than min or max and updated accordingly.&lt;/p&gt;

&lt;p&gt;Once I have everything together, I use &lt;code&gt;ksort()&lt;/code&gt; to bring the &lt;code&gt;$stations&lt;/code&gt; array in order and then echo out the list and calculate the average temperature while doing so (sum / count).&lt;/p&gt;

&lt;p&gt;Running this simple code on my laptop takes &lt;strong&gt;25 Minutes&lt;/strong&gt; 🤯&lt;/p&gt;

&lt;p&gt;Time to optimise and have a look at the profiler:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0oa9bmscw0ftmclu52ar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0oa9bmscw0ftmclu52ar.png" alt="Timeline visualisation using the Datadog PHP profiler showing that the code is 100% CPU bound" width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The timeline visualisation helps me see, that this is clearly CPU bound, file compilation at the beginning of the script is negligible and there are no garbage collection events.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feaqtevjh0xw51brtj8gq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feaqtevjh0xw51brtj8gq.png" alt="Flame graph view showing we are spending 46% CPU time in the  raw `fgetcsv()` endraw  function." width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flame graph view is helpful as well in showing that I am spending 46% of CPU time in &lt;code&gt;fgetcsv()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;fgets()&lt;/code&gt; instead of &lt;code&gt;fgetcsv()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The first optimisation was to use &lt;code&gt;fgets()&lt;/code&gt; to get a line and split on the &lt;code&gt;;&lt;/code&gt; character manually instead of relying on &lt;code&gt;fgetcsv()&lt;/code&gt;. This is because &lt;code&gt;fgetcsv()&lt;/code&gt; does a whole lot more than what I need.&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;// ...&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&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;$city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&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="nv"&gt;$pos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pos&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="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="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally I refactored &lt;code&gt;$data[0]&lt;/code&gt; to &lt;code&gt;$city&lt;/code&gt; and &lt;code&gt;$data[1]&lt;/code&gt; to &lt;code&gt;$temp&lt;/code&gt; everywhere.&lt;/p&gt;

&lt;p&gt;Running the script again with just this one change already brought the runtime down to &lt;strong&gt;19m 49s&lt;/strong&gt;. In absolute numbers, this is still a lot but also: it's &lt;strong&gt;21% down&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1dw496lb9t0f87rv1on.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft1dw496lb9t0f87rv1on.png" alt="Flame graph view now showing  raw `fgets()` endraw ,  raw `substr()` endraw  and  raw `strpos()` endraw  combined using nearly the same amount of CPU as  raw `fgetcsv()` endraw  before, but are still faster" width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flame graph reflects the change, switching to showing the CPU time by line also reveals what is going on in the root frame:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3alzj7h95socs4broq46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3alzj7h95socs4broq46.png" alt="Flame graph showing the CPU time by line, indicating that we are spending 38% in lines 18 and 23" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am spending ~38% CPU time in lines 18 and 23, which are:&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="mi"&gt;18&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="mi"&gt;23&lt;/span&gt; &lt;span class="o"&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;$temp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&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="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line 18 is the first access to the &lt;code&gt;$stations&lt;/code&gt; array in the loop, otherwise it is only an increment and line 23 is a comparison, nothing that seems expensive at first glance, but lets do few more optimisations and you'll see what is taking time here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a reference where possible
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$station&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$station&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$station&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$temp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// instead of&lt;/span&gt;
&lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$data&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should help PHP to not search for the key in the &lt;code&gt;$stations&lt;/code&gt; array on every array access, see it like a cache for accessing the "current" station in the array.&lt;/p&gt;

&lt;p&gt;And it actually helps, running this only takes &lt;strong&gt;17m 48s&lt;/strong&gt;, another &lt;strong&gt;10% down&lt;/strong&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Only one comparison
&lt;/h2&gt;

&lt;p&gt;While looking at the code, I stumbled over this piece:&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$temp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$station&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$station&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="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$temp&lt;/span&gt;&lt;span class="p"&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;$temp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$station&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$station&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="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$temp&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 case the temperature is lower than min, it cannot be higher than max anymore, so I made this an &lt;code&gt;elseif&lt;/code&gt; and maybe spare some CPU cycles.&lt;/p&gt;

&lt;p&gt;BTW: I don't know anything about the order of temperatures in &lt;code&gt;measurements.txt&lt;/code&gt;, but depending on that order it could make a difference if I first checked the one or the other.&lt;/p&gt;

&lt;p&gt;The new versions takes 17m 30s, which is another ~2%. Better than just jitter, but not by a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding type casts
&lt;/h2&gt;

&lt;p&gt;PHP is known as a dynamic language and it is something that I valued a lot when I just got started writing software, one less problem to care about. But on the other side, knowing the types helps the engine make better decisions when running your code.&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;$temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pos&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="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guess what? This simple cast makes the script run in just &lt;strong&gt;13m 32s&lt;/strong&gt; which is a whopping &lt;strong&gt;performance increase of 21%&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1lbrmj9buc9gtcmycb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1lbrmj9buc9gtcmycb9.png" alt="Flame graph showing the CPU time by line, indicating that we are spending 15% in 23" width="800" height="219"&gt;&lt;/a&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="mi"&gt;18&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;$station&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="mi"&gt;23&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$temp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$station&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="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line 18 still shows up with 11% of CPU time spend, which is the access to the array (finding the key in the hash map, which is the underling data structure used for associative arrays in PHP).&lt;/p&gt;

&lt;p&gt;Line 23's CPU time dropped from ~32% to ~15%. This is due to PHP not doing type juggling anymore. Before the type cast, &lt;code&gt;$temp&lt;/code&gt; / &lt;code&gt;$station[0]&lt;/code&gt; / &lt;code&gt;$station[1]&lt;/code&gt; were &lt;code&gt;strings&lt;/code&gt;, so PHP had to convert them to &lt;code&gt;float&lt;/code&gt; in order to compare them on every single comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about JIT?
&lt;/h2&gt;

&lt;p&gt;OPCache in PHP is by default disabled in CLI and needs the &lt;a href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.enable-cli" rel="noopener noreferrer"&gt;&lt;code&gt;opcache.enable_cli&lt;/code&gt; setting&lt;/a&gt; to be set to &lt;code&gt;on&lt;/code&gt;. JIT (as part of OPCache) is default enabled, but effectively disabled as the buffer size is set to &lt;code&gt;0&lt;/code&gt;, so I set the &lt;a href="https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit-buffer-size" rel="noopener noreferrer"&gt;&lt;code&gt;opcache.jit-buffer-size&lt;/code&gt;&lt;/a&gt; to something, I just went with &lt;code&gt;10M&lt;/code&gt;. After these changes have been applied, I re-ran the script with JIT and see it finish in:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7m 19s&lt;/strong&gt; 🚀&lt;/p&gt;

&lt;p&gt;which is &lt;strong&gt;45.9%&lt;/strong&gt; less time spend!!&lt;/p&gt;

&lt;h2&gt;
  
  
  What more?
&lt;/h2&gt;

&lt;p&gt;I already brought the runtime down from 25 minutes in the beginning to just ~7 minutes. One thing that I found absolutely astonishing is that &lt;code&gt;fgets()&lt;/code&gt; allocates ~56 GiB/m of RAM for reading a 13 GB file. Something seems off, so I checked the &lt;a href="https://github.com/php/php-src/blob/7f8465ab22648fb05fa39bacbd82aa48914a2e39/ext/standard/file.c#L884-L931" rel="noopener noreferrer"&gt;implementation of &lt;code&gt;fgets()&lt;/code&gt;&lt;/a&gt; and it looks like that I can spare a lot of these allocations by just omitting the &lt;code&gt;len&lt;/code&gt; argument to &lt;code&gt;fgets()&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="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// instead of&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;999&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;Comparing the profile before and after the change gives this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg77b7f30l235u7t1p9s7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg77b7f30l235u7t1p9s7.png" alt="Profile comparison view before and after omitting the len argument to  raw `fgets()` endraw  revealing that PHP is only allocating 6BiG per minute instead of 56 GiB per minute." width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might think that this gives a lot of performance improvement, but it is just ~1%. This is because those are small allocations which the &lt;a href="https://www.phpinternalsbook.com/php7/memory_management/zend_memory_manager.html#zendmm-internal-design" rel="noopener noreferrer"&gt;ZendMM can handle in bins&lt;/a&gt; and those are blazingly fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can we make it even faster?
&lt;/h2&gt;

&lt;p&gt;Yes, we can! So far my approach was single threaded, which is the nature of most PHP software, but PHP does support threads in user land through the &lt;a href="https://github.com/krakjoe/parallel" rel="noopener noreferrer"&gt;parallel extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Reading the data in PHP is a bottleneck as the profiler clearly shows. Switching from &lt;code&gt;fgetcsv()&lt;/code&gt; to &lt;code&gt;fgets()&lt;/code&gt; and manual split helps, but this is still taking a lot of time, so lets use threads to parallelise reading and processing the data and then afterwards combine the intermediate results from the worker threads.&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="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'measurements.txt'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$threads_cnt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Get the chunks that each thread needs to process with start and end position.
 * These positions are aligned to \n chars because we use `fgets()` to read
 * which itself reads till a \n character.
 *
 * @return array&amp;lt;int, array{0: int, 1: int}&amp;gt;
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_file_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$cpu_count&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="nv"&gt;$size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;filesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&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;$cpu_count&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="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$chunk_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$size&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;$chunk_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$size&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nv"&gt;$cpu_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nv"&gt;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$chunk_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$chunk_size&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;$chunk_end&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;fseek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chunk_end&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// moves fp to next \n char&lt;/span&gt;
            &lt;span class="nv"&gt;$chunk_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$chunks&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nv"&gt;$chunk_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$chunk_end&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="nv"&gt;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$chunk_end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;fclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&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;$chunks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * This function will open the file passed in `$file` and read and process the
 * data from `$chunk_start` to `$chunk_end`.
 *
 * The returned array has the name of the city as the key and an array as the
 * value, containing the min temp in key 0, the max temp in key 1, the sum of
 * all temperatures in key 2 and count of temperatures in key 3.
 *
 * @return array&amp;lt;string, array{0: float, 1: float, 2: float, 3: int}&amp;gt;
 */&lt;/span&gt; 
&lt;span class="nv"&gt;$process_chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$chunk_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$chunk_end&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="nv"&gt;$stations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nv"&gt;$fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;fseek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chunk_start&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&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;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$chunk_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$pos2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&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;$city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&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="nv"&gt;$pos2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pos2&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="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="k"&gt;if&lt;/span&gt; &lt;span class="p"&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;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$station&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nv"&gt;$station&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nv"&gt;$station&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$temp&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;$temp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$station&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="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$station&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="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$temp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$temp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$station&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="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$station&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="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$temp&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nv"&gt;$temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nv"&gt;$temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nv"&gt;$temp&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="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="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nv"&gt;$chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_file_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$threads_cnt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$threads_cnt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&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;$runtime&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;\parallel\Runtime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$futures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$runtime&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;$process_chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;$chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&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="nv"&gt;$chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&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="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;$results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$threads_cnt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// `value()` blocks until a result is available, so the main thread waits&lt;/span&gt;
    &lt;span class="c1"&gt;// for the thread to finish&lt;/span&gt;
    &lt;span class="nv"&gt;$chunk_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$futures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$i&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;value&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk_result&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$city&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$measurement&lt;/span&gt;&lt;span class="p"&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="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$measurement&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$measurement&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&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;$measurement&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="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$result&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="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$result&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="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$measurement&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="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;$measurement&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="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$result&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="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$result&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="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$measurement&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="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;$results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$measurement&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;span class="nb"&gt;ksort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&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;'{'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$results&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$k&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$station&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="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$k&lt;/span&gt;&lt;span class="p"&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;$station&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="s1"&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;$station&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$station&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&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;$station&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="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&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="s1"&gt;'}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code does a few things, first I scan the file and split it into chunks that are &lt;code&gt;\n&lt;/code&gt; aligned (for I am using &lt;code&gt;fgets()&lt;/code&gt; later). When I have the chunks ready, I start &lt;code&gt;$threads_cnt&lt;/code&gt; worker threads that then all open the same file and seek to their assigned chunk start and read and process the data till the chunk end, returning an intermediate result that afterwards gets combined, sorted and printed out in the main thread.&lt;/p&gt;

&lt;p&gt;This multithreaded approach finishes in just:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1m 35s&lt;/strong&gt; 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Is this the end?
&lt;/h2&gt;

&lt;p&gt;Nope, certainly not. There is at least two more things to this solution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I am running this code on MacOS on Apple Silicon hardware which is &lt;a href="https://github.com/php/php-src/issues/13400" rel="noopener noreferrer"&gt;crashing when using the JIT in a ZTS build of PHP&lt;/a&gt;, so the 1m 35s result is without JIT, it might be even faster if I could use it&lt;/li&gt;
&lt;li&gt;I realised that I was running on a PHP version that was compiled using &lt;code&gt;CFLAGS="-g -O0 ..."&lt;/code&gt; due my needs in my day to day work 🤦&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I should have checked this in the beginning, so I re-compiled PHP 8.3 using &lt;code&gt;CFLAGS="-Os ..."&lt;/code&gt; and my final number (with 16 threads) is:&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;27.7 seconds&lt;/strong&gt; 🚀&lt;/p&gt;

&lt;p&gt;This number is by no means comparable to what you can see in the leaderboard of the original challenge and this is due to the fact that I ran this code on total different hardware.&lt;/p&gt;

&lt;p&gt;This is a timeline view with 10 threads:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmbh3qvasrr0iucwijgn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjmbh3qvasrr0iucwijgn.png" alt=" " width="800" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The thread at the bottom is the main thread, waiting for the results from the worker threads. Once those workers have returned their intermediate results the main thread can be seen working on combining and sorting everything. We can also clearly see, that the main thread is by no means the bottleneck. In case you want to try to optimise this even further, concentrate on the worker threads.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did I learn on the way?
&lt;/h2&gt;

&lt;p&gt;Each abstraction layer simply trades usability/integration for CPU cycles or memory. &lt;code&gt;fgetcsv()&lt;/code&gt; is super easy to use and hides a lot of stuff, but this comes at a cost. Even &lt;code&gt;fgets()&lt;/code&gt; hides some stuff from us but makes it super convenient to read the data.&lt;/p&gt;

&lt;p&gt;Adding types to your code will help the language either optimise execution or will stop type juggling which is something you do not see but still pay for it with CPU cycles.&lt;/p&gt;

&lt;p&gt;JIT is awesome, especially when dealing with CPU bound problems!&lt;/p&gt;

&lt;p&gt;It is definitely not the nature of most PHP software, but thanks to parallelisation (using &lt;a href="https://github.com/krakjoe/parallel" rel="noopener noreferrer"&gt;&lt;code&gt;ext-parallel&lt;/code&gt;&lt;/a&gt;) we could push the numbers down significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The END?
&lt;/h2&gt;

&lt;p&gt;I hope you had as much fun reading this article as I had. In case you want to further optimise the code, feel free to grab this and leave a comment how far you got.&lt;/p&gt;

&lt;p&gt;[Edit to add the following on March, 18 2024]&lt;/p&gt;

&lt;h2&gt;
  
  
  There is MOAR!! performance to gain
&lt;/h2&gt;

&lt;p&gt;After this blog post was released, some ideas arose from the comments how to make it faster and there were three suggestions from &lt;a class="mentioned-user" href="https://dev.to/xjakub"&gt;@xjakub&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Remove the &lt;code&gt;isset()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We might not need to check for &lt;code&gt;isset()&lt;/code&gt;, we can "just" create the reference to the station, it will be &lt;code&gt;NULL&lt;/code&gt; when the station does not exist. This means: one array access spared in case the city does exist (which is the majority of cases).&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;# before&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&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;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$station&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// ..&lt;/span&gt;

&lt;span class="c1"&gt;# after&lt;/span&gt;
&lt;span class="nv"&gt;$station&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$stations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$city&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;$station&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="c1"&gt;// ..&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shaves off around 2.5% of wall time!&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't check on the &lt;code&gt;fgets()&lt;/code&gt; return value
&lt;/h3&gt;

&lt;p&gt;The main loop that reads the file currently looks 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="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&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;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$chunk_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The added check for &lt;code&gt;$chunk_start &amp;gt; $chunk_end&lt;/code&gt; came with moving to parallelisation, as every thread only works on a part of the file. Now &lt;a class="mentioned-user" href="https://dev.to/xjakub"&gt;@xjakub&lt;/a&gt; mentioned that there is no need to check for the &lt;code&gt;fgets()&lt;/code&gt; return value anymore, as it will always return a string as long as we are in between &lt;code&gt;$chunk_start&lt;/code&gt; and &lt;code&gt;$chunk_end&lt;/code&gt;, meaning we could make this the expression in the loop and just read unchecked.&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;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$chunk_end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ..&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This changes removes a branch from the loop and results in another ~2.7% drop in wall time!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;fgets()&lt;/code&gt; vs. &lt;code&gt;stream_get_line()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The actual reading and storing in &lt;code&gt;$city&lt;/code&gt; and &lt;code&gt;$temp&lt;/code&gt; looks as followes:&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;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$pos2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&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;$city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&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="nv"&gt;$pos2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pos2&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="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had never heard of &lt;a href="https://www.php.net/manual/de/function.stream-get-line.php" rel="noopener noreferrer"&gt;&lt;code&gt;stream_get_line()&lt;/code&gt;&lt;/a&gt; which behaves nearly identical to &lt;code&gt;fgets()&lt;/code&gt; besides that it allows you to specify the end of line delimiter!&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;$city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;stream_get_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&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;$temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;stream_get_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$chunk_start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$temp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$temp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This change shoved off another &lt;strong&gt;~15%&lt;/strong&gt; of wall time!&lt;/p&gt;

&lt;p&gt;Why is that? The implementations for &lt;code&gt;fgets()&lt;/code&gt; and &lt;code&gt;stream_get_line()&lt;/code&gt; are really close, both are using the PHP stream layer. The major thing that changed is that we do not need to split a string (from &lt;code&gt;fgets()&lt;/code&gt;) into substrings using &lt;code&gt;substr()&lt;/code&gt; anymore. The additional &lt;code&gt;strlen()&lt;/code&gt; call is negligible as variables in PHP are zval's under the hood which hold the length of the string.&lt;/p&gt;

&lt;p&gt;So where are we with the wall time for this PHP script?&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/codemunky"&gt;@codemunky&lt;/a&gt; &lt;a href="https://dev.to/codemunky/comment/2dkmi"&gt;showed up in the comments&lt;/a&gt; and ran the benchmark on the same AX161 from Hetzner that the Java folks used to run their implementations on.&lt;/p&gt;

&lt;p&gt;The final result (so far): &lt;strong&gt;12.76 seconds&lt;/strong&gt; 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  The END again?
&lt;/h2&gt;

&lt;p&gt;I don't know, maybe there's still something to optimize here, but after managing to spend ~83% of the wall time in the &lt;code&gt;stream_get_line()&lt;/code&gt; function, it looks like we've achieved what the PHP stream layer allows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lvxz70ubfn71e2gb3ok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lvxz70ubfn71e2gb3ok.png" alt="PHP Profiler showing 83% wall time spend in  raw `stream_get_line()` endraw  function" width="800" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Either we find another function that bypasses the PHP stream layer and gives more direct access to the filesystem or we try to optimise the layer itself.&lt;/p&gt;

&lt;p&gt;Happy hacking!&lt;/p&gt;

</description>
      <category>php</category>
      <category>programming</category>
    </item>
    <item>
      <title>Revisiting GitLab as a PHP Developer</title>
      <dc:creator>Florian Engelhardt</dc:creator>
      <pubDate>Thu, 18 Aug 2022 08:54:00 +0000</pubDate>
      <link>https://dev.to/realflowcontrol/revisiting-gitlab-as-a-php-developer-5h9l</link>
      <guid>https://dev.to/realflowcontrol/revisiting-gitlab-as-a-php-developer-5h9l</guid>
      <description>&lt;p&gt;Every now and then it seems like a good idea to re-read the documentation of tools you've been using for years. In most cases you just update to the latest version and everything works fine, but you may not be using the latest features.&lt;/p&gt;

&lt;p&gt;So here are some things I already knew, and some things I newly discovered when I re-read GitLab's documentation after stumbling over this tweet:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1461648425884303364-208" src="https://platform.twitter.com/embed/Tweet.html?id=1461648425884303364"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1461648425884303364-208');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1461648425884303364&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;You may see all of the below in action in this &lt;a href="https://gitlab.com/realFlowControl/php-demo/-/merge_requests/3" rel="noopener noreferrer"&gt;demo merge request&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unit test reports
&lt;/h2&gt;

&lt;p&gt;The first thing I found is that GitLab has support for the JUnit XML file format and PHPUnit is able to write a JUnit XML file report. By configuring PHPUnit to create a JUnit XML and by declaring this an artefact in &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; you can have a nice unit test overview in the pipeline itself and the merge request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F698avj6foimimt4zsgul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F698avj6foimimt4zsgul.png" alt="Test summary of your tests, including number of failed and total tests" width="424" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the JUnit logging to your &lt;code&gt;phpunit.xml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;phpunit&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;logging&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;junit&lt;/span&gt; &lt;span class="na"&gt;outputFile=&lt;/span&gt;&lt;span class="s"&gt;"junit.xml"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/logging&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/phpunuit&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And tell GitLab-CI via the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; about this report:&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;Unit Tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./vendor/bin/phpunit&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;reports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;junit.xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you get is a nice overview of the tests run, the time it took to run every single one in the pipeline and in the merge request overview page.&lt;/p&gt;

&lt;p&gt;You may find more hints about this feature in the &lt;a href="https://docs.gitlab.com/ee/ci/testing/unit_test_reports.html" rel="noopener noreferrer"&gt;unit test reports&lt;/a&gt; section of the official GitLab docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test code coverage
&lt;/h2&gt;

&lt;p&gt;GitLab can show you the code coverage in percent you have with your tests at various places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;as a badge via a predefined URL&lt;/li&gt;
&lt;li&gt;in a merge request, also indicating if merging this code would increase or decrease code coverage&lt;/li&gt;
&lt;li&gt;and as a nice graph over time in the &lt;strong&gt;Analytics&lt;/strong&gt; &amp;gt; &lt;strong&gt;Repository&lt;/strong&gt; overview per project and on a group level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhpo874v4j1mxzmycw1hd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhpo874v4j1mxzmycw1hd.png" alt="Overall test code coverage including if this MR will raise or decrease code coverage" width="579" height="85"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Add a simple coverage report to &lt;code&gt;phpunit.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;phpunit&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;coverage&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;report&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;text&lt;/span&gt; &lt;span class="na"&gt;outputFile=&lt;/span&gt;&lt;span class="s"&gt;"php://stdout"&lt;/span&gt;
                  &lt;span class="na"&gt;showUncoveredFiles=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
                  &lt;span class="na"&gt;showOnlySummary=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/report&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/coverage&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/phpunuit&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And tell GitLab-CI via the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; about this report (add the &lt;code&gt;coverage&lt;/code&gt; key):&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;Unit Tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;XDEBUG_MODE=coverage ./vendor/bin/phpunit&lt;/span&gt;
  &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/^\s*Lines:\s*\d+.\d+\%./'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assumes you are using Xdebug and have it already installed in the image your are using to run the tests in GitLab CI. Find the &lt;a href="https://docs.gitlab.com/ee/ci/pipelines/settings.html#add-test-coverage-results-using-coverage-keyword" rel="noopener noreferrer"&gt;documentation about this feature here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test code coverage visualization
&lt;/h2&gt;

&lt;p&gt;This one was an absolut blast for me. While you are gathering code coverage to have a line coverage number you can see everywhere in GitLab, you can also create a line coverage report as Cobertura XML file format with PHPUnit that GitLab can read and show you covered and uncovered lines directly in the merge requests diff view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F658zheke4d5o36cyh0j4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F658zheke4d5o36cyh0j4.png" alt="Diff view with hints if line is covered or not" width="596" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a the cobertura coverage report to &lt;code&gt;phpunit.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;phpunit&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;coverage&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;report&amp;gt;&lt;/span&gt;
            &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;cobertura&lt;/span&gt; &lt;span class="na"&gt;outputFile=&lt;/span&gt;&lt;span class="s"&gt;"cobertura.xml"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/report&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/coverage&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/phpunuit&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And tell GitLab-CI via the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; about this report (add the &lt;code&gt;coverage_report&lt;/code&gt; artifacts key):&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;Unit Tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;XDEBUG_MODE=coverage ./vendor/bin/phpunit&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;reports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;coverage_report&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;coverage_format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cobertura&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cobertura.xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read more about this awesome feature in the &lt;a href="https://docs.gitlab.com/ee/ci/testing/test_coverage_visualization.html" rel="noopener noreferrer"&gt;test coverage visualization&lt;/a&gt; documentation alongside some screenshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code style violations
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cs.symfony.com/" rel="noopener noreferrer"&gt;PHP CS Fixer&lt;/a&gt; is able to generate a GitLab formatted report which GitLab can use to show you any new violations in the code or even if code style violations have been tackled.&lt;/p&gt;

&lt;p&gt;Run PHP CS Fixer with the &lt;code&gt;--format=gitlab&lt;/code&gt; argument and tell GitLab CI via the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; where the report is to be found:&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;Coding guidelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;static analysis&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./vendor/bin/php-cs-fixer fix -v --dry-run --format=gitlab --using-cache=no src/ &amp;gt; gl-cs-fixer.json || exit &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;artifacts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;reports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;codequality&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gl-cs-fixer.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this small overview helps you get more out of GitLab and GitLab CI when working with PHP projects.&lt;/p&gt;

</description>
      <category>php</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>Growing the PHP Core—One Test at a Time</title>
      <dc:creator>Florian Engelhardt</dc:creator>
      <pubDate>Tue, 22 Sep 2020 08:58:27 +0000</pubDate>
      <link>https://dev.to/realflowcontrol/growing-the-php-core-one-test-at-a-time-4g4k</link>
      <guid>https://dev.to/realflowcontrol/growing-the-php-core-one-test-at-a-time-4g4k</guid>
      <description>&lt;p&gt;In September 2000, I started my vocational training at an internet agency with two teams: one doing JavaServer Pages, and one was doing PHP. I was assigned to the PHP team, and when presented with the language, I immediately knew that no one will ever use this. I was wrong. Today, my entire career is built on PHP. It’s time to give back to the community by writing tests for the PHP core itself!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare Your machine!
&lt;/h2&gt;

&lt;p&gt;Before you start writing tests for PHP, let’s start with running the tests that already exist. Fetch the PHP source from GitHub and compile it to do so.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git clone git@github.com:php/php-src.git
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;php-src
&lt;span class="nv"&gt;$ &lt;/span&gt;./buildconf
&lt;span class="nv"&gt;$ &lt;/span&gt;./configure &lt;span class="nt"&gt;--with-zlib&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nt"&gt;-j&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I recommend creating a fork upfront because it makes creating a pull request with your test easier, later on.&lt;/p&gt;

&lt;p&gt;If you do not have a compiler and build tools already installed on your Linux computer, you should install the &lt;code&gt;development-tools&lt;/code&gt; group on Fedora or the &lt;code&gt;build-essential&lt;/code&gt; on Debian Linux. The &lt;code&gt;./configure&lt;/code&gt; command may exit with an error condition; this usually occurs when build requirements are not met, so install whatever &lt;code&gt;./configure&lt;/code&gt; is missing and re-run that step. Keep in mind that you need to install the development packages — in my case, the &lt;code&gt;configure&lt;/code&gt; script stated it was missing &lt;code&gt;libxml&lt;/code&gt;, which was, in fact, installed. What it really missed was the header files, which are in the development package (usually named with a dev or devel suffix). Note that the &lt;code&gt;--with-zlib&lt;/code&gt; is mandatory in this case, as you need the zlib extension which is not built by default.&lt;/p&gt;

&lt;p&gt;After your build is complete, you can find the PHP binary in &lt;code&gt;./sapi/cli/php&lt;/code&gt;. Go ahead and check what you just created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./sapi/cli/php –v
PHP 8.4.0-dev &lt;span class="o"&gt;(&lt;/span&gt;cli&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;built: Feb 21 2024 16:29:11&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;NTS&lt;span class="o"&gt;)&lt;/span&gt;
Copyright &lt;span class="o"&gt;(&lt;/span&gt;c&lt;span class="o"&gt;)&lt;/span&gt; The PHP Group
Zend Engine v4.4.0-dev, Copyright &lt;span class="o"&gt;(&lt;/span&gt;c&lt;span class="o"&gt;)&lt;/span&gt; Zend Technologies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you have a freshly built and running PHP binary, you can finally run the tests included within the GitHub repository. Since PHP 7.4 tests may run in parallel, you can give the number of parallel jobs with the &lt;code&gt;-j&lt;/code&gt; argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nv"&gt;TEST_PHP_ARGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-j&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;
...
&lt;span class="o"&gt;=============================================================&lt;/span&gt;
TEST RESULT SUMMARY
&lt;span class="nt"&gt;-------------------------------------------------------------&lt;/span&gt;
Exts skipped    :   46
Exts tested     :   26
&lt;span class="nt"&gt;-------------------------------------------------------------&lt;/span&gt;
Number of tests : 15670             10668
Tests skipped   : 5002 &lt;span class="o"&gt;(&lt;/span&gt; 31.9%&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--------&lt;/span&gt;
Tests warned    :    0 &lt;span class="o"&gt;(&lt;/span&gt;  0.0%&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;  0.0%&lt;span class="o"&gt;)&lt;/span&gt;
Tests failed    :    0 &lt;span class="o"&gt;(&lt;/span&gt;  0.0%&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;  0.0%&lt;span class="o"&gt;)&lt;/span&gt;
Expected fail   :   32 &lt;span class="o"&gt;(&lt;/span&gt;  0.2%&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;  0.3%&lt;span class="o"&gt;)&lt;/span&gt;
Tests passed    : 10636 &lt;span class="o"&gt;(&lt;/span&gt; 67.9%&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt; 99.7%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;-------------------------------------------------------------&lt;/span&gt;
Time taken      :   76 seconds
&lt;span class="o"&gt;=============================================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks good and it only took 76 seconds to run 10636 tests — quite fast. Zero warnings and zero failures, hooray! You can see that 5002 tests have been skipped and 32 have been expected to fail. You will learn about why this is in the next part.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Do Tests Look Like?
&lt;/h2&gt;

&lt;p&gt;Let’s take a look at what such a test case may look like. PHP test files have the file ending &lt;code&gt;.phpt&lt;/code&gt; and consist of several sections, three of which are mandatory: the &lt;code&gt;TEST&lt;/code&gt;, the &lt;code&gt;FILE&lt;/code&gt; and the &lt;code&gt;EXPECT&lt;/code&gt; or &lt;code&gt;EXPECTF&lt;/code&gt; section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;--TEST--
strlen() function
--FILE--
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello World!'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
--EXPECT--
int(12)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  The TEST Section
&lt;/h4&gt;

&lt;p&gt;This is a short description of what you are testing. It should be as short as possible, as this is what is printed when running the tests. You can put additional details in the &lt;code&gt;DESCRIPTION&lt;/code&gt; section.&lt;/p&gt;

&lt;h4&gt;
  
  
  The SKIPIF Section
&lt;/h4&gt;

&lt;p&gt;This section is optional and is executed by the PHP binary under test. If the resulting output starts with the word &lt;code&gt;skip&lt;/code&gt;, the test case is skipped. If it starts with &lt;code&gt;xfail&lt;/code&gt; the test case is run, but it is expected to fail.&lt;/p&gt;

&lt;h4&gt;
  
  
  The FILE Section
&lt;/h4&gt;

&lt;p&gt;This is the actual test case, PHP code enclosed by PHP tags. This is where you do whatever you want to test. As you have to create output that is then matched against your expectation, you usually find &lt;code&gt;var_dump&lt;/code&gt; all over the place.&lt;/p&gt;

&lt;h4&gt;
  
  
  The EXPECT Section
&lt;/h4&gt;

&lt;p&gt;This section must exactly match the output from the executed code in the &lt;code&gt;FILE&lt;/code&gt; section to pass.&lt;/p&gt;

&lt;h4&gt;
  
  
  The EXPECTF Section
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;EXPECTF&lt;/code&gt; can be used as an alternative to the &lt;code&gt;EXPECT&lt;/code&gt; section and allows the usage of substitution tags you may know from the &lt;code&gt;printf&lt;/code&gt; functions family:&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="o"&gt;--&lt;/span&gt;&lt;span class="no"&gt;EXPECTF&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  The XFAIL Section
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;XFAIL&lt;/code&gt; is another optional section. If present, the test case is expected to fail; you don’t need to echo out &lt;code&gt;xfail&lt;/code&gt; in the &lt;code&gt;SKIPIF&lt;/code&gt; section at all if you have this. It contains a description of why this test case is expected to fail. This feature is mainly used if the test is already finished, but the implementation isn’t yet, or for upstream bugs. It usually contains a link to where a discussion about this topic can be found.&lt;/p&gt;

&lt;h4&gt;
  
  
  The CLEAN Section
&lt;/h4&gt;

&lt;p&gt;This exists so you can clean up after yourself. An example might be temporary files you created during the test. Keep in mind, this section's code is executed independently from the &lt;code&gt;FILES&lt;/code&gt; section, so you have no access to variables declared over there. Also this section is run regardless of the outcome of the test.&lt;/p&gt;

&lt;h4&gt;
  
  
  What else?
&lt;/h4&gt;

&lt;p&gt;You may find more in depth details on the file format at the &lt;a href="http://qa.php.net" rel="noopener noreferrer"&gt;PHP Quality Assurance Team Web Page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Can You Test?
&lt;/h2&gt;

&lt;p&gt;Now that you know what a PHP test looks like it is time to find something to test. Head over to &lt;a href="https://app.codecov.io/github/php/php-src" rel="noopener noreferrer"&gt;codecov.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When I started looking for something to test, I found that the &lt;code&gt;zlib_get_coding_type()&lt;/code&gt; function in &lt;code&gt;ext/zlib/zlib.c&lt;/code&gt; was not covered at all (this was back in the PHP 7.1 branch and on the now retired &lt;a href="http://gcov.php.net/PHP_7_1/lcov_html/ext/zlib/zlib.c.gcov.php#543" rel="noopener noreferrer"&gt;gcov.php.net&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbf9ha1cqkxapkkonjoq7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbf9ha1cqkxapkkonjoq7.png" alt="Code coverage report shows the  raw `zlib_get_coding_type()` endraw  function as completely uncovered." width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next step was to check out what this function was supposed to do in the &lt;a href="https://www.php.net/manual/en/function.zlib-get-coding-type.php" rel="noopener noreferrer"&gt;PHP Documentation&lt;/a&gt;. What I saw in the documentation, but also in the code itself, was that this function returns the string &lt;code&gt;gzip&lt;/code&gt; or &lt;code&gt;deflate&lt;/code&gt; or the Boolean value &lt;code&gt;false&lt;/code&gt;. The linked &lt;a href="https://www.php.net/manual/en/zlib.configuration.php#ini.zlib.output-compression" rel="noopener noreferrer"&gt;zlib.output_compression directive documentation&lt;/a&gt; gave one additional bit of information: the zlib output compression feature reacts on the HTTP &lt;code&gt;Accept-Encoding&lt;/code&gt; header sent with the client HTTP request.&lt;/p&gt;

&lt;p&gt;For the test, this means there are four possible cases to check for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the absence of the Accept-Encoding header&lt;/li&gt;
&lt;li&gt;the Accept-Encoding being &lt;code&gt;gzip&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the Accept-Encoding being &lt;code&gt;deflate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the Accept-Encoding being anything else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last case is treated the same as the first case: The function is expected to return the Boolean value &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time To Write That Test!
&lt;/h2&gt;

&lt;p&gt;Let's start with the first test in the file &lt;code&gt;test/zlib_get_coding_type.phpt&lt;/code&gt; for the case that there is a &lt;code&gt;Accept-Encoding&lt;/code&gt; header set to &lt;code&gt;gzip&lt;/code&gt;. One question that might arise now is: How do I add an HTTP-Request in the tests? The answer is: you don't, but we know that HTTP-Request headers will be exposed via the &lt;code&gt;$_SERVER&lt;/code&gt; super global, so we can inject the fake HTTP header there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;--TEST--
zlib_get_coding_type()
--EXTENSIONS--
zlib
--FILE--
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"HTTP_ACCEPT_ENCODING"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gzip"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zlib.output_compression'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Off'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;zlib_get_coding_type&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zlib.output_compression'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'On'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;zlib_get_coding_type&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
--EXPECT--
bool(false)
string(4) "gzip"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run this single test via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nb"&gt;test &lt;/span&gt;&lt;span class="nv"&gt;TESTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;/zlib_get_coding_type.phpt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sadly, this gives you a failed test. You can easily see why this is the case, by checking the &lt;code&gt;test&lt;/code&gt; directory contents where you find your &lt;code&gt;.phpt&lt;/code&gt; test file and some other files with various file endings. One that is particularly interesting, in this case, is the &lt;code&gt;zlib_get_coding_type.log&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---- EXPECTED OUTPUT
bool(false)
string(4) "gzip"
---- ACTUAL OUTPUT
bool(false)
Warning: ini_set(): Cannot change zlib.output_compression - headers already sent in test/zlib_get_coding_type.php on line 4
bool(false)
---- FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is your first time learning about PHP internals. You cannot change the output compression setting after your script created any form of output. For this to work, there seems to be a handler that is called when we change the &lt;code&gt;zlib.output_compression&lt;/code&gt; directive. Try searching for "zlib.output_compression" in the &lt;code&gt;ext/zlib/zlib.c&lt;/code&gt; source code file. You find it in the call to the &lt;code&gt;STD_PHP_INI_BOOLEAN&lt;/code&gt; macro along with the pointer to the function &lt;code&gt;OnUpdate_zlib_output_compression&lt;/code&gt; in which you can spot the warning you received. To work around this warning, you can change the &lt;code&gt;FILE&lt;/code&gt; section:&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="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"HTTP_ACCEPT_ENCODING"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gzip"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zlib.output_compression'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Off'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$off&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;zlib_get_coding_type&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zlib.output_compression'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'On'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;zlib_get_coding_type&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$off&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$on&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rerunning the test case results in yet another failed test. But this time you know where to look and you will find this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---- EXPECTED OUTPUT
bool(false)
string(4) "gzip"
---- ACTUAL OUTPUT
bool(false)
bool(false)
---- FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The warning is gone, but test failed again. That's progress!&lt;/p&gt;

&lt;p&gt;Looking into the &lt;code&gt;zlib_get_coding_type.log&lt;/code&gt; file, you notice that the second call to the &lt;code&gt;zlib_get_coding_type()&lt;/code&gt; function returned &lt;code&gt;false&lt;/code&gt; and not the expected string &lt;code&gt;gzip&lt;/code&gt;. It seems like PHP is not reacting to the HTTP header that you clearly set just few lines before. Did we spot a bug in PHP?&lt;/p&gt;

&lt;p&gt;The source code tells you: Open the &lt;code&gt;ext/zlib/zlib.c&lt;/code&gt; source code file and search for the variable &lt;code&gt;compression_coding&lt;/code&gt; as this is the variable that is evaluated in the function you are testing. You should find some matches, but there is one at the beginning of the file in the C function &lt;code&gt;php_zlib_output_encoding&lt;/code&gt; which looks like the only place where something is assigned to the variable in question.&lt;/p&gt;

&lt;p&gt;Analysing the source code, not understanding exactly what is going on, and finally asking for help on the &lt;a href="https://phpc.chat" rel="noopener noreferrer"&gt;PHP Community Chat&lt;/a&gt; reveals the following: All of your userspace variables and PHP user-accessible autoglobals (&lt;code&gt;$_GET&lt;/code&gt;, &lt;code&gt;$_SERVER&lt;/code&gt;, ...) are copy-on-write. Altering those from your script creates a copy for you to work on in userspace, effectively &lt;strong&gt;making the HTTP request headers immutable to the userspace&lt;/strong&gt;. The PHP core continues to use the original, unaltered version.&lt;/p&gt;

&lt;p&gt;Now that you know you can not alter or set the HTTP header from within your script to influence PHP’s behavior, this might look like a sad end for your tests code coverage.&lt;/p&gt;

&lt;p&gt;Wait, there is more! I did not tell you about another section in the PHPT file format that might come in handy now: The &lt;code&gt;ENV&lt;/code&gt; section. This section is used to give environment variables to your script and are passed to the PHP process that runs your test code. This means you can go on and that you have to create a test file per test case. Let’s create a new test file for the gzip case and name it &lt;code&gt;zlib_get_coding_type_gzip.phpt&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;--TEST--
zlib_get_coding_type() is gzip
--EXTENSIONS--
zlib
--ENV--
HTTP_ACCEPT_ENCODING=gzip
--FILE--
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zlib.output_compression'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Off'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$off&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;zlib_get_coding_type&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zlib.output_compression'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'On'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$gzip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;zlib_get_coding_type&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$off&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$gzip&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
--EXPECT--
bool(false)
string(4) "gzip"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the test and see it fails once more. You might have expected this by now. 😝&lt;/p&gt;

&lt;p&gt;Let’s see what happened by looking into the &lt;code&gt;zlib_get_coding_type_gzip.log&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---- EXPECTED OUTPUT
bool(false)
string(4) "gzip"
---- ACTUAL OUTPUT
�1�0
    ���B�P�Txꆘ8`5ؑ������s�7a�ze��B+���ϑ�,����^���~�^�J1����ʶ`�\0�v@cm��}�ap�X}��'4�ͩqG�^w
---- FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wow, this looks like some binary garbage, and yeah, this does not match the expected output.&lt;/p&gt;

&lt;p&gt;Look at your test case. You told PHP that you accept gzip encoding and you turned on output compression. PHP is basically just doing what we told it to do. The binary garbage you see here is gzip encoded data. This means you succeeded in activating gzip output compression, but forgot to turn it off before dumping out the two variables. Now, all that’s left is to add another call to deactivate output compression again so the final test looks similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;--TEST--
zlib_get_coding_type() is gzip
--EXTENSIONS--
zlib
--ENV--
HTTP_ACCEPT_ENCODING=gzip
--FILE--
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zlib.output_compression'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Off'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$off&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;zlib_get_coding_type&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zlib.output_compression'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'On'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$gzip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;zlib_get_coding_type&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zlib.output_compression'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Off'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$off&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$gzip&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
--EXPECT--
bool(false)
string(4) "gzip"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this test again, and it finally passes! 🎊🥳🎉&lt;/p&gt;

&lt;p&gt;This leaves you with writing one test case for the &lt;code&gt;Accept-Encoding&lt;/code&gt; header being set to &lt;code&gt;deflate&lt;/code&gt; and another test case for the &lt;code&gt;Accept-Encoding&lt;/code&gt; header being set to an invalid string (basically something that is not &lt;code&gt;gzip&lt;/code&gt; or &lt;code&gt;deflate&lt;/code&gt;). But this is just diligence from this point on and if you like to cheat you can find the tests in the &lt;code&gt;ext/zlib/tests/&lt;/code&gt; directory named &lt;code&gt;zlib_get_coding_type_basic.phpt&lt;/code&gt;, &lt;code&gt;zlib_get_coding_type_br.phpt&lt;/code&gt;, &lt;code&gt;zlib_get_coding_type_deflate.phpt&lt;/code&gt; and &lt;code&gt;zlib_get_coding_type_gzip.phpt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Collect Some Evidence!
&lt;/h2&gt;

&lt;p&gt;Now that you have tests for all four cases you could hope you covered that function with tests or (a way better option if you ask me), you could continue and create a code coverage report! For this to work you need to install the &lt;code&gt;lcov&lt;/code&gt; tool via your Linux package manager (&lt;code&gt;sudo dnf install lcov&lt;/code&gt; for Fedora or &lt;code&gt;sudo apt-get install lcov&lt;/code&gt; for Debian). As PHP was built with &lt;code&gt;gcov&lt;/code&gt; disabled, we need to run configure again, with enabled &lt;code&gt;gcov&lt;/code&gt; and recompile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make clean
&lt;span class="nv"&gt;$ CCACHE_DISABLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./configure &lt;span class="nt"&gt;--with-zlib&lt;/span&gt; &lt;span class="nt"&gt;--enable-gcov&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nt"&gt;-j&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compiles the PHP binary with enabled code coverage generation. Lcov will be used to create an HTML code coverage report afterwards. To create your code coverage report, you need to rerun the tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make &lt;span class="nb"&gt;test &lt;/span&gt;&lt;span class="nv"&gt;TESTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;/zlib_get_coding_type.phpt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While running the tests, &lt;code&gt;gcov&lt;/code&gt; generates coverage files with a &lt;code&gt;gcda&lt;/code&gt; file ending for every source code file. To generate the HTML code coverage report, run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;make lcov
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may find the resulting HTML code coverage report in &lt;code&gt;lcov_html/index.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd0w4ro1jiikjms3u5k01.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fd0w4ro1jiikjms3u5k01.png" alt="Code coverage report now shows 9 of 10 lines covered from the  raw `zlib_get_coding_type()` endraw  function" width="800" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It looks like we covered all four cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s in It for You?
&lt;/h2&gt;

&lt;p&gt;With every test you write PHP becomes more stable and reliable, as functions may not change behavior suddenly between releases.&lt;/p&gt;

&lt;p&gt;Writing tests for PHP gives you a deeper understanding of how your favorite language works internally. In fact, by reading up to this point you learned that HTTP request headers are immutable to userspace, that there is such a thing as userspace, and that there are handler functions called and check what you are doing when you want to change ini settings at runtime.&lt;/p&gt;

&lt;p&gt;Also knowing the PHPT file format gives you other benefits as well: &lt;a href="https://phpunit.de" rel="noopener noreferrer"&gt;PHPUnit&lt;/a&gt; not only supports the PHPT file format and can execute those tests, part of PHPUnit is tested with PHPT test cases. This led me to become a contributor to PHPUnit: Writig tests for PHPUnit in the PHPT file format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shout Out!
&lt;/h2&gt;

&lt;p&gt;I would like to thank everyone involved in the &lt;a href="https://phptestfest.org/start/#whos-behind-this" rel="noopener noreferrer"&gt;PHP TestFest&lt;/a&gt;, but especially &lt;a href="https://twitter.com/ramsey" rel="noopener noreferrer"&gt;Ben Ramsey&lt;/a&gt;, &lt;a href="https://www.sammyk.me/" rel="noopener noreferrer"&gt;Sammy Kaye Powers&lt;/a&gt; and everyone else involved in the organization of the 2017 edition. This was what made me write tests for PHP and made me not only a PHP but also a PHPUnit contributor. In the long run, this brought me my first ElePHPant 🐘!&lt;/p&gt;

&lt;h4&gt;
  
  
  Closing notes
&lt;/h4&gt;

&lt;p&gt;I would like to thank my proof readers &lt;a href="http://www.karaferguson.net/" rel="noopener noreferrer"&gt;Kara Ferguson&lt;/a&gt; and &lt;a href="https://www.pelshoff.com/" rel="noopener noreferrer"&gt;Pim Elsfhof&lt;/a&gt;. It was a pleasure working with you!&lt;/p&gt;

&lt;p&gt;"Wooble" is the property of &lt;a href="https://www.sammyk.me/" rel="noopener noreferrer"&gt;Sammy Kaye Powers&lt;/a&gt; and is used with permission.&lt;/p&gt;

&lt;p&gt;The German version is available through the &lt;a href="https://kiosk.entwickler.de/php-magazin/php-magazin-6-2020/php-core-erweitern-test-fuer-test/" rel="noopener noreferrer"&gt;PHP Magazin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interested in seeing the &lt;a href="https://nomadphp.com/video/305/Growing-the-PHP-Core--one-Test-at-a-Time" rel="noopener noreferrer"&gt;talk to this blog post&lt;/a&gt;? Nomad PHP has you covered.&lt;/p&gt;

</description>
      <category>php</category>
      <category>testing</category>
      <category>contributorswanted</category>
    </item>
    <item>
      <title>Learn how YOU can build a Serverless GraphQL API on top of a Microservice architecture with PHP</title>
      <dc:creator>Florian Engelhardt</dc:creator>
      <pubDate>Mon, 06 May 2019 12:56:40 +0000</pubDate>
      <link>https://dev.to/realflowcontrol/learn-how-you-can-build-a-serverless-graphql-api-on-top-of-a-microservice-architecture-with-php-51bn</link>
      <guid>https://dev.to/realflowcontrol/learn-how-you-can-build-a-serverless-graphql-api-on-top-of-a-microservice-architecture-with-php-51bn</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc60rt0rcu9d3azxlfu7v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc60rt0rcu9d3azxlfu7v.png" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few weeks earlier I read a very interesting article from Chris Noring about &lt;a href="https://dev.to/azure/learn-how-you-can-build-a-serverless-graphql-api-on-top-of-a-microservice-architecture-233g"&gt;“Learn how YOU can build a Serverless GraphQL API on top of a Microservice architecture, part 1”&lt;/a&gt; which he entirely build with Node.js and Express and I thought to myself: &lt;em&gt;you could do this in PHP and&lt;/em&gt; &lt;a href="https://reactphp.org/" rel="noopener noreferrer"&gt;&lt;em&gt;ReactPHP&lt;/em&gt;&lt;/a&gt;&lt;em&gt;!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: This is not a JavaScript vs. PHP rant!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The big picture
&lt;/h3&gt;

&lt;p&gt;As Chris Noring did in his original article, I created two microservices exposing a REST-API, one serving products and another one serving reviews to said products. Those two Microservices will be only available internal and not exposed to the internet, but nevertheless there should be customer facing API. For this purpose I will use GraphQL as a high level API combining the results of the two Services.&lt;/p&gt;

&lt;p&gt;As you can imagine, there is nothing original in my article, I just did the same but with PHP and ReactPHP. You can find the &lt;a href="https://github.com/flow-control/php-graphql-microservice" rel="noopener noreferrer"&gt;source code on GitHub&lt;/a&gt;, so I will limit myself to the highlights.&lt;/p&gt;

&lt;h3&gt;
  
  
  Highlights
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Services
&lt;/h4&gt;

&lt;p&gt;The two services are pretty straight forward. Just a bit of ReactPHP-Code asynchronous handling incoming HTTP-Requests. For the sake of the example both are returning a static JSON-String. In a more in depth example you could connect to a database and look up the data.&lt;/p&gt;

&lt;h4&gt;
  
  
  GraphQL
&lt;/h4&gt;

&lt;p&gt;When using GraphQL in PHP there are a couple of libraries, but only &lt;a href="https://github.com/webonyx/graphql-php" rel="noopener noreferrer"&gt;webonyx/graphql-php&lt;/a&gt; seems mature enough, also it has an asynchronous interface supporting ReactPHP. So this was the perfect match for this case.&lt;/p&gt;

&lt;p&gt;When using the async-Interface, you need to call &lt;code&gt;GraphQL::promiseToExecute&lt;/code&gt; instead of &lt;code&gt;GraphQL::executeQuery&lt;/code&gt; and give it the promise adapter as a first parameter, which in my case was an instance of &lt;code&gt;GraphQL\Executor\Promise\Adapter\ReactPromiseAdapter&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When initialized and used this way the method call to &lt;code&gt;promiseToExecute&lt;/code&gt; will return a ReactPHP Promise, on which you can register your &lt;code&gt;then&lt;/code&gt;-Handler. Also your &lt;code&gt;resolve&lt;/code&gt;-Functions can now return a ReactPHP Promise, which means you can write async code everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you get is a pure asynchronous GraphQL-API written in PHP.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Find the source code at GitHub&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/realFlowControl" rel="noopener noreferrer"&gt;
        realFlowControl
      &lt;/a&gt; / &lt;a href="https://github.com/realFlowControl/php-graphql-microservice" rel="noopener noreferrer"&gt;
        php-graphql-microservice
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Simple example implementation of a GraphQL API on top of a microservice architecture
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;GraphQL API on top of Microservices&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This is just a port from Node.js/Express to PHP/ReactPHP for Chris Norings article on building a &lt;a href="https://dev.to/azure/learn-how-you-can-build-a-serverless-graphql-api-on-top-of-a-microservice-architecture-233g" rel="nofollow"&gt;Serverless GraphQL API on top of a Microservice architecture&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Prerequisites&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;You need to have &lt;code&gt;docker&lt;/code&gt; and &lt;code&gt;docker-compose&lt;/code&gt; up and running on your local machine.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Installing&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/flow-control/php-graphql-microservice.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; php-graphql-microservice
make
docker-compose up -d
curl -X POST \
       -H &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Content-Type: application/json&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; \
       --data &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;{ "query": "{ product (id:1) { id name } }" }&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; \
       localhost:8000&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Build with&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://reactphp.org/" rel="nofollow noopener noreferrer"&gt;ReactPHP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/webonyx/graphql-php" rel="noopener noreferrer"&gt;graphql-php&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/clue/reactphp-buzz" rel="noopener noreferrer"&gt;clue/buzz-react&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;License&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;MIT, see &lt;a href="https://github.com/realFlowControl/php-graphql-microservice/LICENSE" rel="noopener noreferrer"&gt;LICENSE file&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/realFlowControl/php-graphql-microservice" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>async</category>
      <category>php</category>
      <category>reactphp</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Why using REPLACE INTO might be a bad idea</title>
      <dc:creator>Florian Engelhardt</dc:creator>
      <pubDate>Mon, 11 Sep 2017 06:19:27 +0000</pubDate>
      <link>https://dev.to/realflowcontrol/why-using-replace-into-might-be-a-bad-idea-44h1</link>
      <guid>https://dev.to/realflowcontrol/why-using-replace-into-might-be-a-bad-idea-44h1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9hnwb0u3nb2ht3gwbwz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa9hnwb0u3nb2ht3gwbwz.png" alt="Dashboard with a spike" width="800" height="203"&gt;&lt;/a&gt;TTFB over all application servers (dashed line is previous period)&lt;/p&gt;

&lt;p&gt;In our office we have a TV with a performance dashboard, so everyone can see possible problems in the system at any time. Amongst other things we show the &lt;a href="https://en.wikipedia.org/wiki/Time_To_First_Byte" rel="noopener noreferrer"&gt;TTFB&lt;/a&gt; on this dashboard. Those two spikes are not what you want to see, when you are just about to leave the office for lunch.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happened?
&lt;/h3&gt;

&lt;p&gt;We started looking around what’s going on and found out the following in that order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the number of workers on the app-server rose dramatically&lt;/li&gt;
&lt;li&gt;but the CPU consumption did not&lt;/li&gt;
&lt;li&gt;the number of connections to the database reached the upper limit&lt;/li&gt;
&lt;li&gt;but the CPU consumption did not rise (in fact it decreased)&lt;/li&gt;
&lt;li&gt;and finally we start to see the following error messages in our monitoring tools:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deadlock found when trying to get lock; try restarting transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;by that time the system was not responding anymore&lt;/li&gt;
&lt;li&gt;we checked the processlist from the database to see what it is doing&lt;/li&gt;
&lt;li&gt;and found loads of &lt;code&gt;REPLACE INTO&lt;/code&gt; statements and several big &lt;code&gt;DELETE&lt;/code&gt; statements (running since minutes) on the same table&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is so special to that table?
&lt;/h3&gt;

&lt;p&gt;It’s a cache lookup table. Everytime a page gets stored in our frontend cache, we do store some meta information about that cache entry in that database table, mostly dependencies to object data. And when that object data changes (for example the article stock), we fetch all cache entries depending on that object, flush them from the frontend cache and remove the information about it from the lookup table via a simple &lt;code&gt;DELETE&lt;/code&gt; statement.&lt;/p&gt;

&lt;p&gt;New entries in that lookup table are not created via &lt;code&gt;INSERT&lt;/code&gt;, but via a &lt;code&gt;REPLACE INTO&lt;/code&gt; statement. This is because it could happen, that two requests hit the same page, both are a cache miss and both try to create an entry in that lookup table. Easy solution …&lt;/p&gt;

&lt;h3&gt;
  
  
  The InnoDB gap lock
&lt;/h3&gt;

&lt;p&gt;While digging deeper into that problem with our deadlocks, i found an article from Jervin Real from Percona about the &lt;a href="https://www.percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/" rel="noopener noreferrer"&gt;InnoDB gap lock&lt;/a&gt; where he states the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For a non-&lt;code&gt;INSERT&lt;/code&gt; write operation where the &lt;code&gt;WHERE&lt;/code&gt; clause does not match any row, I expected there should’ve been no locks to be held by the transaction, but I was wrong&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Until the moment i read that, i expected the same. But when you read that article from Jervin and the &lt;a href="https://bugs.mysql.com/bug.php?id=1866" rel="noopener noreferrer"&gt;aging bug report&lt;/a&gt; in the MySQL bugzilla about the gap lock, you will understand that this makes perfect sense and you start to think about a &lt;code&gt;REPLACE INTO&lt;/code&gt; statement being the right thing in this situation.&lt;/p&gt;

&lt;h3&gt;
  
  
  How we solved that problem?
&lt;/h3&gt;

&lt;p&gt;We just replaced that &lt;code&gt;REPLACE INTO&lt;/code&gt; statement with a &lt;code&gt;INSERT ON DUPLICATE KEY UPDATE&lt;/code&gt; statement, which does exactly this. If the primary key is found, it updates that record, if not, the record gets inserted. This solved the problems with the deadlocks in that case.&lt;/p&gt;

&lt;p&gt;You think that &lt;code&gt;REPLACE INTO&lt;/code&gt; works exactly that way? Keep on reading.&lt;/p&gt;

&lt;h3&gt;
  
  
  The lesser known problem with &lt;code&gt;REPLACE INTO&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Do you know what &lt;code&gt;REPLACE INTO&lt;/code&gt; really does? Let’s see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&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="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&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="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="n"&gt;fk&lt;/span&gt; &lt;span class="nb"&gt;INT&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="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;VALUES&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="s1"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;VALUES&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;VALUES&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="s1"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What do you think will the result of a &lt;code&gt;SELECT * FROM b;&lt;/code&gt; look like?&lt;/p&gt;

&lt;p&gt;Exactly, the table is empty. This is because the &lt;code&gt;REPLACE INTO&lt;/code&gt; first deletes the found record (which triggers the delete cascade) and then creates a new one. This might be expected behavior, depending on the situation, but to my understanding this would normally not be the intended behavior and at least this was new to me.&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>innodb</category>
      <category>php</category>
      <category>performance</category>
    </item>
    <item>
      <title>Delayed requeuing with RabbitMQ</title>
      <dc:creator>Florian Engelhardt</dc:creator>
      <pubDate>Wed, 12 Apr 2017 13:33:24 +0000</pubDate>
      <link>https://dev.to/realflowcontrol/delayed-requeuing-with-rabbitmq-kc7</link>
      <guid>https://dev.to/realflowcontrol/delayed-requeuing-with-rabbitmq-kc7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffarmc7o5uqn8cpe5jcan.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffarmc7o5uqn8cpe5jcan.jpeg" width="800" height="207"&gt;&lt;/a&gt;Original at &lt;a href="https://www.flickr.com/photos/criminalintent/3226840700/" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://www.flickr.com/photos/criminalintent/3226840700/" rel="noopener noreferrer"&gt;https://www.flickr.com/photos/criminalintent/3226840700/&lt;/a&gt; (CC BY-SA 2.0)&lt;/p&gt;

&lt;h4&gt;
  
  
  The problem
&lt;/h4&gt;

&lt;p&gt;Your consumer can not handle the received message and you need to reject it. If you reject the message with requeue being true, it will be instantly redelivered to your consumer, resulting in very high workload, as your consumer will reject that message. Things have come full circle 😋&lt;/p&gt;

&lt;p&gt;Basicaly you want to reject the message and requeue it, but with a delay.&lt;/p&gt;

&lt;h4&gt;
  
  
  The solution
&lt;/h4&gt;

&lt;p&gt;Well, you could not reject the message, but you could deliver it to another queue from your consumer and write a cronjob moving this messages back to the working queue. But there should be a better solution, and it should be possible to implement without any changes to your consumer or your message broker.&lt;/p&gt;

&lt;h4&gt;
  
  
  The real solution
&lt;/h4&gt;

&lt;p&gt;&lt;a href="http://www.rabbitmq.com/dlx.html" rel="noopener noreferrer"&gt;Dead-Letter-Exchange&lt;/a&gt; (DLX) to the rescue!&lt;/p&gt;

&lt;p&gt;For every queue you can specify a DLX, this is where messages that got rejected or deleted will be delivered to. In combination with a TTL you can automate delivery between queues with a delay.&lt;/p&gt;

&lt;p&gt;To make this work, setup your exchanges and queues like so:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create an exchange named &lt;code&gt;stuff&lt;/code&gt; and direct bind to the queue &lt;code&gt;stuff&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;create an exchange named &lt;code&gt;stuff-retry&lt;/code&gt; and direct bind to the queue &lt;code&gt;stuff-retry&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;create a work queue named &lt;code&gt;stuff&lt;/code&gt; with the &lt;code&gt;x-dead-letter-exchange&lt;/code&gt; header set to &lt;code&gt;stuff-retry&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;create a work queue named &lt;code&gt;stuff-retry&lt;/code&gt; with the &lt;code&gt;x-dead-letter-exchange&lt;/code&gt; header set to &lt;code&gt;stuff&lt;/code&gt; and the &lt;code&gt;x-message-ttl&lt;/code&gt; header to &lt;code&gt;300000&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it. If your consumer listening on &lt;code&gt;stuff&lt;/code&gt; now rejects a message without requeue, it will be delivered to the &lt;code&gt;stuff-retry-exchange&lt;/code&gt; which passes the message on to the &lt;code&gt;stuff-retry-queue&lt;/code&gt;. After five minutes in that queue, the messages dies and will be redelivered to the &lt;code&gt;stuff-exchange&lt;/code&gt;, which passes the message on to the &lt;code&gt;stuff-queue&lt;/code&gt; so your consumer can process that message.&lt;/p&gt;

</description>
      <category>rabbitmq</category>
      <category>php</category>
      <category>pubsub</category>
    </item>
    <item>
      <title>Block SSH brute-force attacks</title>
      <dc:creator>Florian Engelhardt</dc:creator>
      <pubDate>Fri, 19 Aug 2016 09:53:32 +0000</pubDate>
      <link>https://dev.to/realflowcontrol/block-ssh-brute-force-attacks-1n1o</link>
      <guid>https://dev.to/realflowcontrol/block-ssh-brute-force-attacks-1n1o</guid>
      <description>&lt;p&gt;… or at least slow them down (using iptables on Linux)&lt;/p&gt;

&lt;p&gt;When having a server with the port 22 open to the internet, you will find a sheer endless number of login tries from various sources. 99% of these are just &lt;a href="https://en.wikipedia.org/wiki/Brute-force_search" rel="noopener noreferrer"&gt;brute-force&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Dictionary_attack" rel="noopener noreferrer"&gt;dictionary&lt;/a&gt; attacks.&lt;/p&gt;

&lt;p&gt;As I do not want my log files to boil over with all these failed logins, I searched for a solution to block them out or at least slow them down.&lt;/p&gt;

&lt;p&gt;Here is what I did (thanks &lt;a class="mentioned-user" href="https://dev.to/baptistecs"&gt;@baptistecs&lt;/a&gt; for the missing &lt;code&gt;ACCEPT&lt;/code&gt; and adding IPv6)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# cleanup&lt;/span&gt;
iptables &lt;span class="nt"&gt;-F&lt;/span&gt;
iptables &lt;span class="nt"&gt;-X&lt;/span&gt; SSH_CHECK

ip6tables &lt;span class="nt"&gt;-F&lt;/span&gt;
ip6tables &lt;span class="nt"&gt;-X&lt;/span&gt; SSH_CHECK

&lt;span class="c"&gt;# set rules&lt;/span&gt;
iptables &lt;span class="nt"&gt;-N&lt;/span&gt; SSH_CHECK
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; SSH_CHECK &lt;span class="nt"&gt;-m&lt;/span&gt; recent &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; SSH
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; SSH_CHECK &lt;span class="nt"&gt;-m&lt;/span&gt; recent &lt;span class="nt"&gt;--update&lt;/span&gt; &lt;span class="nt"&gt;--seconds&lt;/span&gt; 60 &lt;span class="nt"&gt;--hitcount&lt;/span&gt; 2 &lt;span class="nt"&gt;--name&lt;/span&gt; SSH &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; SSH_CHECK &lt;span class="nt"&gt;-m&lt;/span&gt; recent &lt;span class="nt"&gt;--update&lt;/span&gt; &lt;span class="nt"&gt;--seconds&lt;/span&gt; 3600 &lt;span class="nt"&gt;--hitcount&lt;/span&gt; 10 &lt;span class="nt"&gt;--name&lt;/span&gt; SSH &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; SSH_CHECK &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 22 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="c"&gt;# accept packet if not previously dropped&lt;/span&gt;

ip6tables &lt;span class="nt"&gt;-N&lt;/span&gt; SSH_CHECK
ip6tables &lt;span class="nt"&gt;-A&lt;/span&gt; SSH_CHECK &lt;span class="nt"&gt;-m&lt;/span&gt; recent &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; SSH
ip6tables &lt;span class="nt"&gt;-A&lt;/span&gt; SSH_CHECK &lt;span class="nt"&gt;-m&lt;/span&gt; recent &lt;span class="nt"&gt;--update&lt;/span&gt; &lt;span class="nt"&gt;--seconds&lt;/span&gt; 60 &lt;span class="nt"&gt;--hitcount&lt;/span&gt; 2 &lt;span class="nt"&gt;--name&lt;/span&gt; SSH &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
ip6tables &lt;span class="nt"&gt;-A&lt;/span&gt; SSH_CHECK &lt;span class="nt"&gt;-m&lt;/span&gt; recent &lt;span class="nt"&gt;--update&lt;/span&gt; &lt;span class="nt"&gt;--seconds&lt;/span&gt; 3600 &lt;span class="nt"&gt;--hitcount&lt;/span&gt; 10 &lt;span class="nt"&gt;--name&lt;/span&gt; SSH &lt;span class="nt"&gt;-j&lt;/span&gt; DROP
ip6tables &lt;span class="nt"&gt;-A&lt;/span&gt; SSH_CHECK &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 22 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="c"&gt;# accept packet if not previously dropped&lt;/span&gt;

iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-s&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="c"&gt;# whitelist your IP (replace 127.0.0.1)&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 22 &lt;span class="nt"&gt;-m&lt;/span&gt; state &lt;span class="nt"&gt;--state&lt;/span&gt; NEW &lt;span class="nt"&gt;-j&lt;/span&gt; SSH_CHECK &lt;span class="c"&gt;# jump from INPUT to SSH_CHECK&lt;/span&gt;

ip6tables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-s&lt;/span&gt; ::1/128 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="c"&gt;# whitelist your IP (replace ::1/128)&lt;/span&gt;
ip6tables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 22 &lt;span class="nt"&gt;-m&lt;/span&gt; state &lt;span class="nt"&gt;--state&lt;/span&gt; NEW &lt;span class="nt"&gt;-j&lt;/span&gt; SSH_CHECK &lt;span class="c"&gt;# jump from INPUT to SSH_CHECK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am creating a new chain called &lt;code&gt;SSH_CHECK&lt;/code&gt; to which i pass on every TCP packet received on port 22 that is in state new (aka syn packet). The source IP address is logged and if the same address occurs two or more times in the last minute or ten or more times in the last hour that packet is dropped.&lt;/p&gt;

&lt;p&gt;I also added a rule to bypass the &lt;code&gt;SSH_CHECK&lt;/code&gt; chain if the source IP address equals 127.0.0.1 / ::1/128 (replace that with your static IP or just ditch that line).&lt;/p&gt;

&lt;p&gt;To be honest, I am not blocking brute-force attacks, but the attacker (or attacking script) can only try one password per minute or nine passwords per hour.&lt;/p&gt;

&lt;p&gt;And at last: it is generally a good advice to turn of password authentication in your ssh server as well as root login. Add the following to your &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; file or change it to read as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>security</category>
      <category>ssh</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
