<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts | BackEndTea</title><link>https://backendtea.com/post/</link><atom:link href="https://backendtea.com/post/index.xml" rel="self" type="application/rss+xml"/><description>Posts</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en</language><image><url>https://backendtea.com/media/icon_hu_a0dc1595079f9789.png</url><title>Posts</title><link>https://backendtea.com/post/</link></image><item><title>Immutable by default: How to avoid hidden state bugs in OOP</title><link>https://backendtea.com/post/immutable-by-default/</link><pubDate>Mon, 18 Aug 2025 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/immutable-by-default/</guid><description>&lt;p>One of the harder bugs to debug are bugs dealing with mutable data. A while ago we ran into a problem where the &lt;code>last_modified&lt;/code> date of entities was always 1 day off. See if you can spot the issue in the code below:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyEntity&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">public&lt;/span> &lt;span class="nx">DateTime&lt;/span> &lt;span class="nv">$lastModified&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">saveEntity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Notifier&lt;/span> &lt;span class="nv">$notifier&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$now&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">DateTime&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$entity&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">MyEntity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$now&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$notifier&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">notifyAt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$now&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">modify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;+1 day&amp;#39;&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The problem here is that the &lt;code>$now-&amp;gt;modify()&lt;/code> call mutates (changes) the data of the original object. Since both &lt;code>$now&lt;/code> and &lt;code>$entity-&amp;gt;lastModified&lt;/code> point to the &lt;em>same&lt;/em> &lt;code>DateTime&lt;/code> object in memory, the &lt;code>$entity-&amp;gt;lastModified&lt;/code> is also updated.&lt;/p>
&lt;p>If we had used &lt;code>DateTimeImmutable&lt;/code>, the &lt;code>modify()&lt;/code> method would have returned a new object instead of changing the original. The original &lt;code>DateTimeImmutable&lt;/code> would stay untouched, meaning &lt;code>$lastModified&lt;/code> wouldn’t be updated.&lt;/p>
&lt;p>In this example these lines are close to each other, but in reality the entity, and the notifier code were in entirely different places, yet they still used the same &lt;code>DateTime&lt;/code> object, thus causing the bug. This cost us multiple hours of debugging, and is why I default to immutable objects.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="what-does-immutable-mean">What does (im)mutable mean?&lt;/h2>
&lt;p>Immutable means your data &lt;strong>cannot&lt;/strong> be changed, and mutable means it &lt;strong>can&lt;/strong> be changed. When talking about immutability there is a difference between deep and shallow immutability. Shallow means the reference itself is immutable, deep means the reference and everything inside is immutable.&lt;/p>
&lt;p>Let&amp;rsquo;s take the JavaScript &lt;code>const&lt;/code> and &lt;code>let&lt;/code> as an example. &lt;code>const&lt;/code> variables are immutable, and &lt;code>let&lt;/code> variables are mutable, meaning we can override a &lt;code>let&lt;/code>, but not a &lt;code>const&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">immutable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">immutable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// TypeError: invalid assignment to const &amp;#39;immutable&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kd">let&lt;/span> &lt;span class="nx">mutable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">mutable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Allowed.
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>However, &lt;code>const&lt;/code> in JavaScript only gives shallow immutability, meaning that we can change the data inside of it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">immutable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prop&lt;/span>: &lt;span class="kt">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">immutable&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Allowed, since we don&amp;#39;t change `immutable`, but its contents
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">immutable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">prop&lt;/span>: &lt;span class="kt">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span> &lt;span class="c1">// TypeError: invalid assignment to const &amp;#39;immutable&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We can (almost) achieve deep immutability with &lt;code>Object.freeze&lt;/code>. This marks each property as immutable. However, nested objects can still be modified if they are not frozen as well.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">immutable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">freeze&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="nx">prop&lt;/span>: &lt;span class="kt">1&lt;/span>&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">immutable&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">prop&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// TypeError: Cannot assign to read only property &amp;#39;prop&amp;#39; of object
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">nested&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">freeze&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="nx">outer&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nx">inner&lt;/span>: &lt;span class="kt">1&lt;/span>&lt;span class="p">}});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">nested&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">outer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">inner&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Allowed, since the `inner` object is not frozen.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">nested&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">freeze&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="nx">outer&lt;/span>: &lt;span class="kt">Object.freeze&lt;/span>&lt;span class="p">({&lt;/span> &lt;span class="nx">inner&lt;/span>: &lt;span class="kt">1&lt;/span>&lt;span class="p">})});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">nested&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">outer&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">inner&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// TypeError: Cannot assign to read only property &amp;#39;inner&amp;#39; of object
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Most languages have ways to achieve either shallow or deep immutability. It&amp;rsquo;s good to know which one you are dealing with so you don&amp;rsquo;t make any wrong assumptions.&lt;/p>
&lt;h2 id="advantages-of-immutable-data">Advantages of immutable data&lt;/h2>
&lt;h3 id="easier-to-reason-about-bugs">Easier to reason about bugs&lt;/h3>
&lt;p>Mutable nested objects can be silently changed from anywhere in your code, making it harder to find the root cause of a bug.&lt;/p>
&lt;p>To illustrate this, let&amp;rsquo;s look at a more complete version of the example at the beginning of this post. When reading the &lt;code>Persister&lt;/code> code, nothing seems out of the ordinary, and when looking at usages of &lt;code>$entity-&amp;gt;lastModified&lt;/code>, nothing comes up either. You&amp;rsquo;d have to check inside the &lt;code>Notifier&lt;/code> class to see the modification happening, and know its the same instance as the &lt;code>$entity-&amp;gt;lastModified&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Notifier&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">notifyAfter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">DateTime&lt;/span> &lt;span class="nv">$now&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$seconds&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">24&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="mi">60&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="mi">60&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$dateTime&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$now&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">modify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;+&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nv">$seconds&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> seconds&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Schedule the notification
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyEntity&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">public&lt;/span> &lt;span class="nx">DateTime&lt;/span> &lt;span class="nv">$lastModified&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Persister&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">private&lt;/span> &lt;span class="nx">Notifier&lt;/span> &lt;span class="nv">$notifier&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">persist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">DateTime&lt;/span> &lt;span class="nv">$currentTime&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">MyEntity&lt;/span> &lt;span class="nv">$entity&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">notifier&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">notifyAfter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$currentTime&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Persistence logic
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$persister&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Persister&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">Notifier&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// The entity is created with the current time
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nv">$now&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">DateTime&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$persister&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">persist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$now&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">MyEntity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$now&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we had used a &lt;code>DateTimeImmutable&lt;/code> instead, then it would have been impossible to change the inner data of the &lt;code>$entity-&amp;gt;lastModified&lt;/code> from anywhere else. Since the modify method would just return a new instance, rather than change the original data.&lt;/p>
&lt;h3 id="concurrency">Concurrency&lt;/h3>
&lt;p>When you are using concurrency (for example, with &lt;code>async await&lt;/code> in JavaScript), and using mutable data, then it&amp;rsquo;s easy to create race conditions, where it becomes impossible to reason about the end result of the code. Take the code below, where all promises are changing the &lt;code>data.count&lt;/code> value. Since one may take longer than the other, it&amp;rsquo;s impossible to determine what the end result of &lt;code>data.count&lt;/code> is going to be.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">getRandomInt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">max&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">floor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">Math&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">random&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">max&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">executeAfter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">callback&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">void&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">milliSeconds&lt;/span>: &lt;span class="kt">number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">resolve&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">setTimeout&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">resolve&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">callback&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nx">milliSeconds&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">count&lt;/span>: &lt;span class="kt">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">await&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">all&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">executeAfter&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="nx">getRandomInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">executeAfter&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="nx">getRandomInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">executeAfter&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="nx">getRandomInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">executeAfter&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="nx">getRandomInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">executeAfter&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="nx">getRandomInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">count&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// Impossible to guess what this value is going to be.
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Instead we could have each promise return the new count, and use the last value.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="nx">Promise&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">all&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">executeAfter&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">getRandomInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">executeAfter&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">getRandomInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">executeAfter&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">getRandomInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">executeAfter&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">getRandomInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">executeAfter&lt;/span>&lt;span class="p">(()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">getRandomInt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">results&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">length&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">]);&lt;/span> &lt;span class="c1">// Always 5
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Moving from mutable to immutable objects may require some refactoring. The changes required will depend on your use case. But usually returning a value instead of mutating a property is a good starting point.&lt;/p>
&lt;h2 id="when-not-to-use-immutability">When not to use immutability&lt;/h2>
&lt;p>While immutability has clear benefits, there are situations where mutability is the better choice.&lt;/p>
&lt;h3 id="performance-overhead">Performance overhead&lt;/h3>
&lt;p>Creating new objects instead of modifying existing ones has a cost. Take the 2 classes below for example. Calling &lt;code>$mutable-&amp;gt;add()&lt;/code> 1 million times takes about &lt;code>150-170ms&lt;/code> on my machine. Calling &lt;code>$immutable-&amp;gt;add()&lt;/code> 1 million times, takes about &lt;code>300ms&lt;/code>. If every microsecond counts, then the overhead caused by a pattern where you return a new object every time, may not be worth it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Mutable&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$counter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">add&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">counter&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Immutable&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$counter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">add&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">self&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">counter&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="short-lived-builder-objects">Short-lived builder objects&lt;/h3>
&lt;p>If you are using a &lt;a href="https://backendtea.com/post/every-day-design-pattern-builder/">builder design pattern&lt;/a>, then it is generally understood that you are making changes to this object directly, and that you are mutating state. And while you &lt;em>could&lt;/em> use an immutable builder, it&amp;rsquo;s generally okay to go with mutable data, as the builder object is controller and short-lived. It even helps in keeping the created object immutable, as you have done all modifications needed in the builder phase.&lt;/p>
&lt;h2 id="writing-immutable-objects">Writing immutable objects&lt;/h2>
&lt;p>Using keywords in your language of choice like &lt;code>const&lt;/code>, &lt;code>readonly&lt;/code>, or &lt;code>final&lt;/code> isn&amp;rsquo;t the only thing you need to change when writing immutable objects. There are a few patterns that you&amp;rsquo;ll likely see when writing or interacting with immutable objects.&lt;/p>
&lt;h3 id="with-instead-of-set">With instead of set&lt;/h3>
&lt;p>Mutable objects usually have setters, which directly change the object. When you want to get rid of those, but still need a way to change data, you can use the &amp;ldquo;with&amp;rdquo; pattern.
A &lt;code>setX&lt;/code> method will change &lt;code>x&lt;/code> on the object. A &lt;code>withX&lt;/code> method will return a new object where &lt;code>X&lt;/code> is changed to the new value.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyMutableClass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$count&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">setCount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$newCount&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$newCount&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">setName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$newName&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$newName&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">readonly&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">MyImmutableClass&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$count&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">withCount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$newCount&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">self&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$newCount&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">name&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">withName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$newName&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">self&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">count&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$newName&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>The &lt;code>DateTime&lt;/code> bug I opened this post with isn&amp;rsquo;t a rare occurrence, it can happen any time you deal with mutable state. If we had used immutability, the bug couldn’t have existed. That’s why I default to immutable objects, and only use mutability when I truly have to.&lt;/p>
&lt;p>Immutability isn’t always the easiest path, but it’s almost always the safer one. So go and make the next class you write immutable. You might never go back.&lt;/p>
&lt;p>Do you agree that immutable should be the default? Or do you prefer the flexibility of mutable objects? Let me know in the comments.&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Types are documentation</title><link>https://backendtea.com/post/types-are-documentation/</link><pubDate>Fri, 10 Jan 2025 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/types-are-documentation/</guid><description>&lt;p>Adding types to your PHP code base is more than just runtime validation. It helps both developers and
static analysis tools, by making the code clearer and easier to understand. Adding types makes code more
self-explanatory and gives you a form of documentation.&lt;/p>
&lt;p>A function with proper types will help clarify what it is supposed to do. It becomes easier to determine what a function
does from reading its signature. Take the following function as an example. Can you figure out what
this method does?&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">validateUserIsLoggedIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$user&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There are a few ways this function could work. It can return &lt;code>true&lt;/code> or &lt;code>false&lt;/code>, telling you whether the user is logged in. It could
throw an exception if the user is not logged in. It could even return a result object with information. By adding types we know which
case we are dealing with, without having to read the function code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Returns whether the user is logged in
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">function&lt;/span> &lt;span class="nf">validateUserIsLoggedIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">User&lt;/span> &lt;span class="nv">$user&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">bool&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Will throw an exception if the user is not logged in.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @throws UserNotLoggedIn
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">validateUserIsLoggedIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">User&lt;/span> &lt;span class="nv">$user&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Will return a result object (or enum)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">function&lt;/span> &lt;span class="nf">validateUserIsLoggedIn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">User&lt;/span> &lt;span class="nv">$user&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">UserLoginStatus&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By having these types, we know what the method does. If there are multiple &amp;lsquo;versions&amp;rsquo; of this method, we can figure out which one is the
best for our use case.&lt;/p>
&lt;p>But we can do better than just return types. We can specify whether a method expects something has been checked, or if it assumes that already has been done.
For example, do we need to check that name passed to the &lt;code>setUserName&lt;/code> is not empty, or does the method check that itself.
And of course, we get the added benefit, that if we do pass a possibly empty string to &lt;code>setUserName&lt;/code>, then tooling like PHPStan will
tell us that we made a mistake.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">User&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Do we need to validate before passing, or does the method do that?
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">setUserName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param non-empty-string $name
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">setUserName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By adding a &lt;code>non-empty-string&lt;/code> type to the name, we signal that we are not going to check inside of the method that this
parameter is empty. It also helps us inside of the function, as we now know that it can&amp;rsquo;t be empty, so we don&amp;rsquo;t need to &amp;lsquo;deal&amp;rsquo;
with that edge case.&lt;/p>
&lt;p>Ready to take your PHP code to the next level? Start adding types today and see the difference!&lt;/p>
&lt;p>If you want to get notified of my next blog post, join the newsletter&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Why I prefer array functions over loops</title><link>https://backendtea.com/post/functions-over-loops/</link><pubDate>Mon, 12 Aug 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/functions-over-loops/</guid><description>&lt;p>I&amp;rsquo;ve come to prefer using array functions over &lt;code>foreach&lt;/code> loops in PHP. And while the syntax isn&amp;rsquo;t &lt;em>as&lt;/em> nice as in
JavaScript, I still think they work better than a &amp;rsquo;normal&amp;rsquo; loop.&lt;/p>
&lt;h2 id="what-are-array-functions">What are array functions&lt;/h2>
&lt;p>In PHP you have &lt;a href="https://www.php.net/manual/en/ref.array.php" target="_blank" rel="noopener">quite some array functions&lt;/a>, each with their own use.
For this post I&amp;rsquo;m talking about the functions that loop over an array, and execute a function for each item in that array.&lt;/p>
&lt;p>You can think of functions like &lt;code>array_reduce&lt;/code>, &lt;code>array_map&lt;/code>, &lt;code>array_filter&lt;/code> etc. Each of these functions does a specific thing
for each element of that array, and returns a new value.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="why-use-an-array-function">Why use an array function&lt;/h2>
&lt;h3 id="communicate-intent">Communicate intent&lt;/h3>
&lt;p>I think the most important factor for me is the fact that with an array function you are communicating your intent.
The function call makes it clear what you are trying to achieve. &lt;code>array_filter&lt;/code>? You&amp;rsquo;re filtering your array. &lt;code>array_map&lt;/code>?
You are changing each element of the array, but keeping its structure. &lt;code>array_reduce&lt;/code>? You are changing your array to a new structure.&lt;/p>
&lt;p>This means I can see what someone is trying to do by just reading the function call, so that reduces the mental space I need
in order to understand the function&lt;/p>
&lt;h3 id="everything-happens-inside-the-function">Everything happens inside the function&lt;/h3>
&lt;p>Everything that the array function does happens inside the function. Take a look at the next example.
In the loop part, the &lt;code>$newNumbers&lt;/code> is not part of the loop, and lives above it. While in this example its not
that big of a deal, I&amp;rsquo;ve seen the declaration of the new variable being miles away form the loop, due to refactors, and
other things happening.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$newNumbers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$array&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nv">$key&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$newNumbers&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nv">$key&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$number&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$newNumbers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fn&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$number&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$number&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="less-chance-for-side-effects">Less chance for side effects&lt;/h3>
&lt;p>In PHP we can write 2 type of closures. with the &lt;code>function($parameter) use($uses)&lt;/code> and the &lt;code>fn($parameters)&lt;/code> syntax.
Since the second one only allows 1 statement, the chance of having side effects is much lower already.
And in the first example, anything that isn&amp;rsquo;t passed in as a function needs to be added through the &lt;code>use()&lt;/code>.
This means we can clearly see what this loop is using. And the &lt;code>use&lt;/code> doesn&amp;rsquo;t pass values by reference (by default).
So if you change a variable in the loop, it resets again for the next iteration.
And there is no chance of accidentally overriding a variable that lives outside the loop.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$new&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$array&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nv">$value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$new&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$value&lt;/span> &lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// $value is now the last item of the array;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$value&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">},&lt;/span> &lt;span class="nv">$array&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// $value is still 3;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="when-not-to-use-an-array-function">When not to use an array function&lt;/h2>
&lt;p>These functions aren&amp;rsquo;t always the best solution. For example, if you need to do both a filter and a map, then you
can do this in a single &lt;code>foreach&lt;/code> loop. But if you use &lt;code>array_filter&lt;/code> and &lt;code>array_map&lt;/code>, this will actually loop over the array twice.
While this probably isn&amp;rsquo;t a huge bottleneck for most applications, if you have millions of elements this can make a difference.
For example, looping over 100k elements, where the filter call filtered out 10%, I got a difference of &lt;code>0.002&lt;/code> to &lt;code>0.003&lt;/code> seconds in runtime
on my machine.&lt;/p>
&lt;p>And sometimes you just need the function to manipulate multiple variables, and have the side effects, in that case, using
a loop is probably better.&lt;/p>
&lt;p>But, if you have a clear case of having to filter an array, change the elements to something else, or having to &amp;lsquo;reduce&amp;rsquo; it to
another structure, using an array function wil keep it more readable, maintainable, and less prone to bugs that a loop.&lt;/p>
&lt;p>If you want to get notified of my next blog post, join the newsletter&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Being a software engineer is a lot like being a butcher</title><link>https://backendtea.com/post/software-engineer-is-like-being-a-butcher/</link><pubDate>Wed, 07 Aug 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/software-engineer-is-like-being-a-butcher/</guid><description>&lt;p>I was a butcher for roughly four years before I became a software engineer. (And nowadays, I&amp;rsquo;m a vegetarian.)
And while one of the jobs doesn&amp;rsquo;t require you to work inside a refrigerator half the time,
there are actually quite some similarities in how we keep our work environment clean.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="keeping-your-work-environment-clean">Keeping your work environment clean&lt;/h2>
&lt;p>Both as a butcher and as a software engineer I see four different cleaning times/types.&lt;/p>
&lt;p>The first type is the cleaning as you&amp;rsquo;re working. As a butcher things will get dirty when you&amp;rsquo;re working on your
workbench, so you keep it clean by throwing out some trash, removing marinade, putting things back where they belong etc.
As a software engineer this is kind of the same as &lt;a href="https://backendtea.com/post/boyscout-rule">the boyscout rule&lt;/a>. Where you work on a piece of
code, and you add some types, document a function, maybe refactor something to make it more readable etc.&lt;/p>
&lt;p>The second one is unplanned cleaning when there is some time to spare during the day. In the butchery I worked they called
this a &amp;ldquo;tussendtijdse opruming or T.T.O.&amp;rdquo; which is Dutch for an intermittent cleaning. If we had some extra time to spare
we would do a bit more thorough cleanup of either a specific workbench, or of the entire place. Where we clean it up
a bit better, to make sure we stay in line with health and safety regulation. This would also allow us to work faster
again after the cleanup, as we had more space to work, everything was where it was supposed to be etc.&lt;/p>
&lt;p>As a software engineer we have this as well. Sometimes you have a litte bit of time to spare during a sprint, where you can
do a bit of a larger refactor, maybe replace a deprecated library with a new one etc. While not necessarily planned, these
clean ups are a bit larger than just touching a few things while you&amp;rsquo;re working on it.&lt;/p>
&lt;p>The third type of clean up as a butcher was the end of day cleaning. Here we would do a thorough clean of the place, using
water and soap to clean up all the workbenches. We would hose down everything (twice), and get everything set up again
for the next day.&lt;/p>
&lt;p>As a software engineer we don&amp;rsquo;t have a &amp;ldquo;closing&amp;rdquo; hour, but we do have the end of a sprint or a period.
You should always reserve some time at the end (or start) of a sprint, to do some cleaning. You can create tickets
as you&amp;rsquo;re working on something for potential clean ups. Maybe a business critical piece of software is extremly
confusing to read. So if you have some time you can create good tests for it and rewrite it to be more readable.
This is the type of work that is planned, but shouldn&amp;rsquo;t take more than a few days to complete.&lt;/p>
&lt;p>The last type of cleanup as a butcher was the type of cleaning we would do once or twice a year. During the time of
year when it was less busy, we&amp;rsquo;d take time to maybe clean the freezer. We could scrape off all the ice, do a full
clean, and throw out anything that looked suspicious. We&amp;rsquo;d also clean parts you normally couldn&amp;rsquo;t reach, like the wall
behind a storage rack.&lt;/p>
&lt;p>In software engineering, this is the type of cleanup that takes a longer time, and is planned in advance. This can be things
like replacing a library that is at the core of your business, which has a large impact to replace.
Things like these can take an entire sprint, but are needed to keep your codebase up to date, and keep it functioning
for years to come.&lt;/p>
&lt;p>If you want to get notified of my next blog post, join the newsletter&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Break the rules! (but only if you understand them)</title><link>https://backendtea.com/post/break-the-rules/</link><pubDate>Wed, 31 Jul 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/break-the-rules/</guid><description>&lt;p>There are a lot of &amp;lsquo;rules&amp;rsquo; in software engineering.
Don&amp;rsquo;t copy-paste your code, don&amp;rsquo;t use &lt;code>goto&lt;/code>, your code should be easily understandable, and many more.
Generally these rules make sense, but every once in a while someone will have a hot take, and post about how
you should copy-paste your code everywhere, how goto is the greatest invetion ever, or how variable names should be shorter.&lt;/p>
&lt;p>I&amp;rsquo;d like to advocate for a middle ground, and say that &amp;lsquo;it depends&amp;rsquo; (which is true for almost anything in software).
While most of the time you shouldn&amp;rsquo;t copy-paste your code, there are cases where it makes sense. And while goto is generally
a bad idea, &lt;a href="https://github.com/search?q=repo%3Asymfony%2Fsymfony&amp;#43;goto&amp;#43;language%3APHP&amp;amp;type=code&amp;amp;l=PHP" target="_blank" rel="noopener">the symfony code bases uses it&lt;/a>.&lt;/p>
&lt;h2 id="when-can-you-break-the-rules">When can you break the rules&lt;/h2>
&lt;p>Personally I &lt;em>try&lt;/em> to only break a rule if the following are all true:&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="i-understand-why-this-rule-is-in-place">I understand why this rule is in place&lt;/h2>
&lt;p>With the example of copy-pasting code, I&amp;rsquo;d like to think at this point I understand why this rule is in place.
If you copy-paste code all over your code base, you reduce maintainability. You make it harder to make a change, instead of
having to update it one place, you have to update your code at every place you copy-pasted it to.&lt;/p>
&lt;h2 id="there-is-an-actual-benefit-to-breaking-this-rule">There is an actual benefit to breaking this rule&lt;/h2>
&lt;p>Often people will break a rule, just because it easy at that moment. Copy-pasting your code is easier than making
a good abstraction to re-use. But sometimes there are benefits. For example, sometimes you want to copy-paste your code
if there is not &amp;lsquo;right&amp;rsquo; abstraction. If 2 pieces of code to the same thing, but that by coincidence, then an abstraction
doesn&amp;rsquo;t make sense.&lt;/p>
&lt;h2 id="the-benefits-outweigh-the-downsides">The benefits outweigh the downsides&lt;/h2>
&lt;p>Even if there is a benefit to breaking the rule, it has to outweigh the negatives. And this really depends on the goals
for your code base. For example if you&amp;rsquo;re writing a piece if code that gets executed a million times per request,
then, the performance of that piece of code &lt;em>may&lt;/em> be more important than the readability. But it comes down to
what is more important for that piece of code.&lt;/p>
&lt;h2 id="closing-thoughts">Closing thoughts&lt;/h2>
&lt;p>A lof of the times I&amp;rsquo;ll decide not to break a rule at because there isn&amp;rsquo;t an actual benefit (besides it just being easy),
or because while there is a benefit, it doesn&amp;rsquo;t outweigh the downsides. The first check is mostly there to check if
I actually know what I&amp;rsquo;m doing. Especially if I&amp;rsquo;m doing something new, like trying another language, or working in a new
paradigm, then understanding the rule comes into play. If you&amp;rsquo;re just starting out with something, try to stick to the
rules, until you understand the up and downsides of that rule.&lt;/p>
&lt;p>What rules do you break sometimes, and how do you determine which rules to break? Let me know in the comments!&lt;/p>
&lt;p>If you want to get notified of my next blog post, join the newsletter&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>PHP 8.2 changes to iterator functions I missed</title><link>https://backendtea.com/post/php-82-iterator-all-iterable/</link><pubDate>Tue, 30 Jul 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/php-82-iterator-all-iterable/</guid><description>&lt;p>This week I learned of a small change that got introduced in PHP 8.2 that I completely missed.
The &lt;code>iterator_*()&lt;/code> functions got an update, which make them actually usable when dealing with the &lt;code>iterable&lt;/code> type.
Now this is only really relevant if you are dealing with iterables that might not be arrays, but you need to use them
as an array.&lt;/p>
&lt;p>The big change is that a function like &lt;code>iterator_to_array&lt;/code>, it now accepts the &lt;code>iterable&lt;/code> type, rather than just &lt;code>\Traversable&lt;/code>.
For some code I&amp;rsquo;m working on it means I can refactor a lot of code to remove &lt;code>is_array&lt;/code> checks all over the place.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Pre PHP 8.2:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">function&lt;/span> &lt;span class="nf">getAllStringLengths&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">iterable&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">is_array&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$input&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">iterator_to_array&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$input&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strlen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Post PHP 8.2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">function&lt;/span> &lt;span class="nf">getAllStringLengths&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">iterable&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strlen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nx">iterator_to_array&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$input&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>"Just use Psl for that"</title><link>https://backendtea.com/post/just-use-psl-for-that/</link><pubDate>Tue, 23 Jul 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/just-use-psl-for-that/</guid><description>&lt;p>Something I&amp;rsquo;ve been hearing myself says in every project that I have installed &lt;a href="https://github.com/azjezz/psl" target="_blank" rel="noopener">Psl&lt;/a> in, is &amp;ldquo;Just use Psl for that&amp;rdquo;.
And while some bits and bobs like &lt;code>array_find&lt;/code> are getting into PHP 8.4, the library does so much more.
In this post I&amp;rsquo;ll go over the bits that I use often, and maybe convince you to give it a try as well.&lt;/p>
&lt;h2 id="what-is-psl">What is Psl&lt;/h2>
&lt;p>Psl is a php composer package that can be found &lt;a href="https://github.com/azjezz/psl" target="_blank" rel="noopener">right here&lt;/a>.&lt;/p>
&lt;p>To quote the Psl readme:&lt;/p>
&lt;blockquote>
&lt;p>Psl is a standard library for PHP, inspired by hhvm/hsl.
The goal of Psl is to provide a consistent, centralized, well-typed set of APIs for PHP programmers.&lt;/p>&lt;/blockquote>
&lt;p>And that is what it does, from async operations, to working with the filesystem, to hashing, it provides a clean api, that
is consistent and well typed, and integrates well with static analysis tools like PHPStan and Psalm.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="useful-psl-parts">Useful Psl parts&lt;/h2>
&lt;h3 id="type">Type&lt;/h3>
&lt;p>In my opinion the &lt;code>Type&lt;/code> namespace is probably the most useful part of Psl. Very often we have to validate some data that
comes from some input where we are (almost) guaranteed what type it is, but we can&amp;rsquo;t be 100% certain. Here the &lt;code>Type&lt;/code> namespace
of Psl comes into play. Let&amp;rsquo;s say you have a specific structure you expect to receive from and API, and you want to validate
that it is in fact correct. With Psl that would look something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">Psl\Type&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$userResponse&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">fetchUsers&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$userResponse&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Type\shape&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;users&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">Type\vec&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Type\shape&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">Type\int&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">Type\nullable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Type\string&lt;/span>&lt;span class="p">()),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;email&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">Type\string&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;roles&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">Type\vec&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Type\string&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">])),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">])&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">coerce&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$userResponse&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now after this line, you are guaranteed that &lt;code>$userResponse&lt;/code> is an array, with the key &lt;code>users&lt;/code>, which is a list of
arrays, containing the keys specified, with their respective types. This is a runtime check that will error out if the type
does not match. It also helps PHPStan and Pslam understand the structure of the data.&lt;/p>
&lt;p>Each function in the &lt;code>Type&lt;/code> namespace returns a &lt;code>Type&lt;/code> object that you can use to either &lt;code>coerce&lt;/code> or &lt;code>assert&lt;/code> your data.
&lt;code>assert&lt;/code> checks that your data 100% matches, and then return that. Meaning that &lt;code>Type\int()-&amp;gt;assert('1')&lt;/code> wil fail.
&lt;code>coerce&lt;/code> will try to coerce your data, meaning that &lt;code>Type\int()-&amp;gt;coerce('1')&lt;/code>, will return the integer &lt;code>1&lt;/code>.
There is also the &lt;code>matches&lt;/code> function, which returns a bool of whether your type matches.&lt;/p>
&lt;h3 id="dict-vec--iter">Dict, Vec &amp;amp; Iter&lt;/h3>
&lt;p>These three namespaces have a lot of overlap. Dict is for Dictionaries, Vec for lists, and Iter for general iterables.
They contain all kinds of usefull array/iterable functions that you need, that aren&amp;rsquo;t present in PHP natively, or
require multiple function calls.&lt;/p>
&lt;p>For example if I want to filter out all elements that are 3 or smaller, but keep my list as a list, I need to do an
&lt;code>array_filter&lt;/code>, and then don&amp;rsquo;t forget to wrap it in an &lt;code>array_values&lt;/code>. With Psl, since the &lt;code>Vec&lt;/code> namespace is about lists,
it makes it a list, regardless of whether it was a list before, or even if it was a Generator before, without any extra steps
required.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">Psl\Vec&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Native
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">array_values&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">array_filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$myArray&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$input&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Psl
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">Vec\filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$myArray&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$input&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>One function I tend to use a lot from the &lt;code>Dict&lt;/code> namespace is &lt;code>Dict\reindex&lt;/code>. What this does is reindex an array given a
callback. Which you can do with something like &lt;code>array_reduce&lt;/code>, or in a foreach loop, but having a dedicated function to
do some makes the code a lot more readable in my opinion.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$users&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">42&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;foo&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">24&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;bar&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$indexedUsers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Dict\reindex&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$users&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">fn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$user&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$user&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// $indexedUsers is now:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">42&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">42&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;foo&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">24&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;id&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">24&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;bar&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And these few namespaces aren&amp;rsquo;t even scratching the surface of the functions this library provides.
Although I currently don&amp;rsquo;t have to deal with &lt;code>Async&lt;/code> operations in an existing codebase, if I needed to, I would
probably check out Psl first.&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Why you should be typing your arrays in PHP</title><link>https://backendtea.com/post/php-typed-arrays/</link><pubDate>Wed, 17 Jul 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/php-typed-arrays/</guid><description>&lt;p>We have been able to natively type parameters of methods and functions in PHP for quite some time.
In basically any version of PHP you &lt;em>should&lt;/em> be running we can do something like the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">getNames&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$item&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$item&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getName&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="basic-array-types">Basic array types&lt;/h2>
&lt;p>This however tells us very little about the types that we are dealing with.
When specifying a type as just &lt;code>array&lt;/code> or &lt;code>iterable&lt;/code>, or something alike, all you are saying is that
it is an array, but you really want to specify that it is an array containing a specific type.
If we rewrite the previous example to be more specific, we can do the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param array&amp;lt;User&amp;gt; $input
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @return array&amp;lt;string&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">getNames&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$item&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$item&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getName&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we add types like this we now understand we are getting an array which contains &lt;code>User&lt;/code> objects, and
we are returning an array of &lt;code>string&lt;/code>. There are multiple benefits to doing so.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="benefits-of-typed-arrays">Benefits of typed arrays&lt;/h2>
&lt;h3 id="ide-hints">IDE hints&lt;/h3>
&lt;p>The first benefit is the fact that your IDE will now understand what kind of array this is, and thus be able
to give you autocompletion inside the function. It will also help you when trying to find the usages of &lt;code>getName()&lt;/code>
on the &lt;code>User&lt;/code> class, as now your IDE, knows it is that method that is being called.&lt;/p>
&lt;p>This will also help you with refactor tools, for example if you are renaming the &lt;code>getName&lt;/code> method, an IDE like PHPStorm
will be able to also update this method call. You&amp;rsquo;ll also get errors in your IDE if you pass in the wrong type of array,
or if you try to do something that&amp;rsquo;s not possible with the returned array of strings.&lt;/p>
&lt;h3 id="static-analysis-tooling">Static analysis tooling&lt;/h3>
&lt;p>The second benefit comes from the usage of static analysis (SA) tooling (like PHPStan). SA tools will understand
what you are doing, and give you error in your pipelines if you are doing something that shouldn&amp;rsquo;t be possible.
The more types you use in your codebase, the better understanding it has of your code, the better it can help you
detect issues before they hit production.&lt;/p>
&lt;p>In PHPStan adding type declarations becomes a requirement on level 6. This may be hard to add everywhere in legacy code bases,
but I would highly recommend upgrading to at least this level and generating a baseline. This will require you to at least
add these types to any new code you are writing&lt;/p>
&lt;h3 id="developer-understanding">Developer understanding&lt;/h3>
&lt;p>Most likely you aren&amp;rsquo;t the only one working on your code base. If the next developer comes along and sees the &lt;code>getNames&lt;/code>
method, they first have to figure out what the input is, before they can figure out what they are supposed to do with the method.&lt;/p>
&lt;p>I can&amp;rsquo;t count the amount of times I looked at a method and thought &amp;ldquo;I have no idea what&amp;rsquo;s going on here&amp;rdquo;, because there were
either no types, or just &lt;code>array&lt;/code> as a type. I would first have to search through the code base to see where the method is called,
before being able to understand what the parameters where and what the return values where.&lt;/p>
&lt;h2 id="advanced-array-types">Advanced array types&lt;/h2>
&lt;p>With modern PHP tooling, we can however, go much further than just doing &lt;code>@param array&amp;lt;Type&amp;gt; $value&lt;/code>.
We can do lists, specify array keys, or make sure it is not empty&lt;/p>
&lt;h3 id="the-list-type">The list type&lt;/h3>
&lt;p>We can for example specify something is a &lt;code>list&lt;/code>.&lt;/p>
&lt;p>A list is an array, where the keys start at &lt;code>0&lt;/code>, and each time they increment by one.
The list type can be especially useful when converting to &lt;code>JSON&lt;/code> for example, as that will make it an array, instead of an object.
If you create an array without keys, it is always a list, but you can also specify the keys if needed&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$list&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$alsoList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">0&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">1&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">2&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Keys start at 1, not at 0, so not a list.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nv">$notList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">1&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">2&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">3&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Has a string key, so not a list.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nv">$alsoNotList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;foo&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;bar&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>list&lt;/code> type doesn&amp;rsquo;t exist natively, but you can use it like so, and a tool like PHPStan will help tell you if
you aren&amp;rsquo;t passing a list&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param list&amp;lt;User&amp;gt; $input
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @return list&amp;lt;string&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">getNames&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$item&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$item&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getName&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="specifying-keys">Specifying keys&lt;/h3>
&lt;p>Nowadays, we can even specify that we expect a specific set of keys, or just what type the keys should be. With the following
notation we specify literal keys: &lt;code>array{key: Type, key2: Type}&lt;/code>.
This also comes with IDE autocompletion for those keys, and all other previously mentioned advantages.
If we want to specify just the general types, for example if the keys are strings, we can do that as follows:
&lt;code>array&amp;lt;string, Type&amp;gt;&lt;/code>. Generally the notation for arrays is &lt;code>array&amp;lt;KeyType, ValueType&amp;gt;&lt;/code>. If we only pass one type, it is the type of the value.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param array{begin: int, interval: int} $data
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @return int
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">transform&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;begin&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;interval&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param array&amp;lt;string, mixed&amp;gt; $data
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @return string
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">concatKeys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$data&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nv">$key&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$v&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$output&lt;/span> &lt;span class="o">.=&lt;/span> &lt;span class="nv">$key&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$output&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="non-empty-types">Non-empty types&lt;/h3>
&lt;p>We can even go as far as to specify the array should not be empty. you can do that by using &lt;code>non-empty-array&lt;/code> (or &lt;code>non-empty-list&lt;/code>),
instead of just &lt;code>array&lt;/code> (or &lt;code>list&lt;/code>).
If we take the &lt;code>concatKeys&lt;/code> function as an example, we could define it like so, to make sure it is never empty. And we also specify
the returned &lt;code>string&lt;/code> will never be &lt;code>empty&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param non-emptyarray&amp;lt;string, mixed&amp;gt; $data
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @return non-empty-string
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">concatKeys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$data&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nv">$key&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$v&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$output&lt;/span> &lt;span class="o">.=&lt;/span> &lt;span class="nv">$key&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$output&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And once again, this provides better understanding for SA tooling, your IDE, and other developers.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Adding types to your arrays (and just adding types in general) helps you when using an IDE, or static analysis, and
helps other developers in your team to understand your code. It should help you produce less bugs, and make your code
cleaner and more readable.&lt;/p>
&lt;p>These types help you document the intent of a function and how it should be used. And even better, with modern day tooling,
it even shows you if you are doing something wrong.&lt;/p>
&lt;p>Are you using (array) types in your PHP codebase, and do you think they are usefull? Are you already using more
advanced types like the &lt;code>list&lt;/code> and &lt;code>non-empty-array&lt;/code>. Let me know in the comments!&lt;/p>
&lt;p>If you want to get notified of the &lt;a href="https://backendtea.com/post/phpunit-your-first-tests/">next blog post&lt;/a>, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>How to fix "connect to host github.com port 22: Operation timed out"</title><link>https://backendtea.com/post/ssh-could-not-connect/</link><pubDate>Tue, 09 Jul 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/ssh-could-not-connect/</guid><description>&lt;h2 id="the-problem">The problem&lt;/h2>
&lt;p>This weekend I was at a holiday resort. In the morning I wanted to
do a little bit of work, and encountered the following error when I tried to pull a repo.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ git pull
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ssh: connect to host github.com port 22: Operation timed out
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fatal: Could not &lt;span class="nb">read&lt;/span> from remote repository.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Please make sure you have the correct access rights
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">and the repository exists.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>At first, I assumed my internet had a hick-up, so I just ran the command again, only to be greeted by the same
error message as before.&lt;/p>
&lt;p>As it turns out, the holiday resort I was in was blocking basically anything that wasn&amp;rsquo;t normal http requests, which use port 80 and 443 for http and https.&lt;/p>
&lt;h2 id="connecting-ssh-over-another-port">Connecting ssh over another port&lt;/h2>
&lt;p>Thankfully both GitHub and GitLab offer a solution for this problem. Both have an alternative ssh server, that you can ssh to on port 443.
To use it, you need to change your ssh config, which you can find in &lt;code>~/.ssh/config&lt;/code>.&lt;/p>
&lt;p>Add the following bits in your ssh config for either GitLab or GitHub. The &lt;code>Host &amp;lt;url&amp;gt;&lt;/code> part tells us the next bit of config is for that host.
The &lt;code>Hostname&lt;/code> part makes your ssh use this for host instead of the original one. The &lt;code>User&lt;/code> key give it the user (which is &lt;code>git&lt;/code> by default, but
we specify it to be sure. And finally the &lt;code>Port&lt;/code> is the bit that helps us out, as we will now ssh over port 443.
If you attempt to just change the SSH port to 443, but keep the normal host, that unfortunately won&amp;rsquo;t work, as the normal servers only accept ssh over port 22.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"># GitLab config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Host gitlab.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Hostname altssh.gitlab.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> User git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Port 443
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># GitHub config
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Host github.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Hostname ssh.github.com
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Port 443
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> User git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once your holiday is over, and your back home, you can just remove the config you added, and you&amp;rsquo;re back on
the original GitHub or GitLab servers.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>What is PHP's declare(strict_types=1); and why you should use it</title><link>https://backendtea.com/post/php-declare-strict-types/</link><pubDate>Thu, 04 Jul 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/php-declare-strict-types/</guid><description>&lt;p>You may have had people tell you that you should use &lt;code>declare(strict_types=1);&lt;/code> in your PHP files, but
what exactly does it do, and why should (or shouldn&amp;rsquo;t) you use it?&lt;/p>
&lt;h2 id="history">History&lt;/h2>
&lt;p>A long, long time ago PHP didn&amp;rsquo;t have scalar types, or even return types. In PHP 7.0 both of these got introduced.
In 5.6 and before we had to use annotations, and couldn&amp;rsquo;t add it in the language. (If you still see code like this it&amp;rsquo;s
probably from that era.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string $input
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @return string
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> **/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">var_dump&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$input&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Nowadays we can use scalar types, return types, property types. We even have unions and intersection types now. Which
means we can write the same code from before like so.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">var_dump&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$input&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="using-declarestrict_types1">Using declare(strict_types=1)&lt;/h2>
&lt;p>But what do you think happens if we then call this method with an integer, or a boolean like so?
Will this give us a &lt;code>TypeError&lt;/code>, will it cast the type or will it stay the original type?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">2.5&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">true&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To determine what happens we need to look at the top of the file that is doing the calls. If we see &lt;code>declare(strict_types=1)&lt;/code>,
then a &lt;code>TypeError&lt;/code> will be thrown when we run this code. If you see nothing related to &lt;code>strict_types&lt;/code> or &lt;code>declare(strict_types=0);&lt;/code>
Then the type will be cast, and we would get the following output:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">string(1) &amp;#34;3&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">string(3) &amp;#34;2.5&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">string(1) &amp;#34;1&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="why-you-should-use-declare-strict-types">Why you should use declare strict types&lt;/h2>
&lt;p>Unintended type casts can result in unexpected behavior. If we have a function that takes a boolean, but we pass it a
string, any truthy string will become &lt;code>true&lt;/code>. So if you get user input from a JSON POST for example, and you pass the
incorrectly typed data, it will always be interpreted as true, and may cause hard to uncover bugs.&lt;/p>
&lt;p>However, if you use &lt;code>declare(strict_types=1);&lt;/code> everywhere in your code base, you instead have a &lt;code>TypeError&lt;/code> being thrown,
causing execution of your code to stop, and (hopefully) your monitoring going off. So while it may break your website for a
bit, it will help you figure out the issue much faster and reduce weird behaviour.&lt;/p>
&lt;p>When something breaks you want it to break loudly. It&amp;rsquo;s generally better to have an exception or error being thrown when
unexpected issues arise, rather than ignore it and continue your program, even if everything is going wrong. Because before
you know it, you have wrong data in the database, or even emails being sent to customers when it wasn&amp;rsquo;t intended.&lt;/p>
&lt;p>For example, we had an older part of the code base that handled the login flow.
The login method looked something like this, and had no types as it was originally written for PHP 5.X.
Now the types here are actually wrong, for password a &lt;code>string&lt;/code> (namely the password itself), was
provided if there was one, and false was passed if it was a login without a password (a log in with google button for example).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string $method
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string $userName
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param bool $password
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">login&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$userName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$password&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>At some point we had to do some refactors in this part of the codebase, and decided to add types.
And suddenly, no one could log in anymore with a password. Any password they passed was just cast to &lt;code>true&lt;/code>,
and then later when that was checked with &lt;code>password_verify&lt;/code>, the 2 passwords didn&amp;rsquo;t match.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">login&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$userName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bool&lt;/span> &lt;span class="nv">$password&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we had &lt;code>declare(strict_types=1);&lt;/code> at the top of this file, we would&amp;rsquo;ve had a &lt;code>TypeError&lt;/code>.
This does mean customers would&amp;rsquo;ve seen an &lt;code>Oops something went wrong!&lt;/code> type of screen.
It also would&amp;rsquo;ve meant our alerting would&amp;rsquo;ve gone off, and the change would&amp;rsquo;ve been rolled back, while
we looked into the issue. With a stack trace it would&amp;rsquo;ve been easy to spot where this went wrong, and
we could have updated the type like so. But because we didn&amp;rsquo;t have that, we had to spend hours debugging
why customers reported not being able to log in.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">login&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$userName&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bool&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$password&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="caveats-of-strict_types-in-php">Caveats of strict_types in PHP&lt;/h2>
&lt;p>As I mentioned earlier, you have to look at where the code is calling from.&lt;/p>
&lt;p>Let&amp;rsquo;s say we have 2 files, &lt;code>functions.php&lt;/code> and &lt;code>index.php&lt;/code>, where one has &lt;code>declare(strict_types=1)&lt;/code>, and the other doesn&amp;rsquo;t.
In this case, no &lt;code>TypeError&lt;/code> is thrown, as the code that is executing the &lt;code>takesString&lt;/code>, isn&amp;rsquo;t using &lt;code>strict_types&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//functions.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">declare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strict_types&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">takesString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//index.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">require_once&lt;/span> &lt;span class="no">__DIR__&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s1">&amp;#39;/functions.php&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">takesString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// no error
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But if we now turn this around and make &lt;code>index.php&lt;/code> strictly typed, then we get a &lt;code>TypeError&lt;/code>, this is because the
code that is executing &lt;code>takesString&lt;/code> is now in &lt;code>strict_type&lt;/code> mode.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//functions.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">takesString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//index.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">declare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strict_types&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">require_once&lt;/span> &lt;span class="no">__DIR__&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s1">&amp;#39;/functions.php&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">takesString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// throws a TypeError
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="how-to-use-declarestrict_types1-in-php">How to use declare(strict_types=1) in PHP&lt;/h2>
&lt;p>If you decide to use &lt;code>strict_types&lt;/code> in PHP, then I would advise to use it everywhere in your code base.
When you are using tools like &lt;code>php-cs-fixer&lt;/code> or &lt;code>php code sniffer&lt;/code>, then you can add checks to make it required and add
it to the whole code base at once. That may not always be possible for a legacy project. In that case you could use a tool like
PHPStan to make it required, and add all files where you currently can&amp;rsquo;t add strict types to the baseline.
See &lt;a href="https://github.com/ergebnis/phpstan-rules/pull/79/files#diff-61d46fa0c1581edc238aecdad5a2e7f3c41e88e3f1ae33f7f6b8fb1e8999edad" target="_blank" rel="noopener">this rule&lt;/a>
on how to do that.&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Adding PHPStan to a legacy project</title><link>https://backendtea.com/post/phpstan-legacy-project/</link><pubDate>Thu, 27 Jun 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/phpstan-legacy-project/</guid><description>&lt;h2 id="phpstan">PHPStan&lt;/h2>
&lt;p>PHPStan is a static analysis tool for PHP. It helps you find problems in your code base, like passing wrong types,
calling methods that don&amp;rsquo;t exist, detect wrong annotations, and so much more.&lt;/p>
&lt;p>I would argue you shouldn&amp;rsquo;t be writing PHP code these days without having a static analyzer to help you. It reduces
the errors you create, and speeds up your development significantly.&lt;/p>
&lt;h2 id="how-to-add-phpstan">How to add PHPStan&lt;/h2>
&lt;h3 id="1-composer-install">1: Composer install&lt;/h3>
&lt;p>The first thing you&amp;rsquo;re gonna have to do with PHPStan is to install it. You can run the following command to install it.
You will need PHP 7.2 to run and install it, but it can analyze older code if needed.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ composer require --dev phpstan/phpstan
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-configuration">2: Configuration&lt;/h3>
&lt;p>You can get started with a really basic configuration file, which is called &lt;code>phpstan.neon&lt;/code>.
The relevant config goes below &lt;code>parameters&lt;/code>. We&amp;rsquo;re starting with level &lt;code>0&lt;/code>, which is the lowest level of PHPStan.&lt;/p>
&lt;p>At level 0, we are doing very basic checks, like checking for unknown classes/methods, the wrong amount of arguments
passed to a method.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># phpstan.neon&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">parameters&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">level&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">paths&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">src/&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">tests/&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h3 id="3-your-first-phpstan-run">3: Your first PHPStan run&lt;/h3>
&lt;p>Once you have PHPStan installed, and a config file set up, you can run PHPStan like this&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ vendor/bin/phpstan a
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now if your project is set up correctly, and you have no problems at level 0, you should see an error like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> [OK] No errors
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But, chances are that your code might not be understandable by PHPStan just yet, and you get errors.&lt;/p>
&lt;h4 id="unknown-classes-due-to-custom-autoloader">Unknown classes due to custom autoloader&lt;/h4>
&lt;p>If you have a custom autoloader set up, you can tell PHPStan to use that like follows.
This will help PHPStan find the relevant classes. (This file will be executed by PHPStan, so make sure it has no side effects).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">parameters&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bootstrapFiles&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">custom_autoloader.php&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want PHPStan to know about a certain file or directory, but not analyze it, you can add them as follows.
This will let PHPStan find all the classes, functions etc from these directories, but not report errors in them.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">parameters&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scanFiles&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">ThirdParty.class.php&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">scanDirectories&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./my_classes/custom&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And finally, if you have a phar that gets included, that has classes, you can make that phar a bootstrap file as well.
This lets PHPStan find its symbols. If you have a PHPUnit phar for example, you can add it like so:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">parameters&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">bootstrapFiles&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">phpunit.phar&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="4-adding-it-to-your-ci">4: Adding it to your CI&lt;/h3>
&lt;p>If you already have a CI set up, adding PHPStan shouldn&amp;rsquo;t be too difficult.
I would make sure you have no errors on level 0 before doing so. If there are too many errors check the part of this post
about the &lt;a href="">baseline&lt;/a>. &lt;!-- TODO: baseline link. -->
You can run &lt;code>vendor/bin/phpstan a&lt;/code> in your CI, like you would a PHPUnit or any other tool.&lt;/p>
&lt;p>At the level 0 we just created, PHPStan won&amp;rsquo;t have much impact, but it&amp;rsquo;s good to have it running in your CI already.
This makes it so that once we start making PHPStan more strict, we make sure that the rest of your team needs to follow
the rules from PHPStan.&lt;/p>
&lt;h3 id="5-increasing-the-phpstan-levels">5: Increasing the PHPStan levels&lt;/h3>
&lt;p>At this point you have PHPStan running in your CI, which means that the real work begins. Depending on your code base,
just level 2 can already be a difficult task. If at all possible, try to fix all the errors of a level and then move onto the next one.
Do this until you start running into too many errors to fix at once. Depending on your code base this can be level 2, 5, 6 or even higher.&lt;/p>
&lt;p>Bumping the level is as easy as changing the level value in your config file.
Or you can pass the &lt;code>--level X&lt;/code> flag when you run PHPStan, to use that level for a single call.&lt;/p>
&lt;h3 id="6-adding-a-baseline">6: Adding a baseline&lt;/h3>
&lt;p>At this point you have PHPStan in your CI, but the amount of errors to fix at once is becoming too much to fix at once.
Now you can start introducing a baseline. In PHPStan the baseline is a file containing all your errors,
which PHPStan will ignore for you. Now ignoring all the errors may seem counterintuitive at first. You want to fix
the problems PHPStan is finding for you, so why ignore it?&lt;/p>
&lt;p>The baseline isn&amp;rsquo;t about fixing all the errors you currently have. Instead, it makes sure that your &lt;strong>new&lt;/strong> code follows
the rules you set up. By using a baseline we can increase the checks PHPStan is doing, without having to fix all our code at once.
So all the new code you write, and code you change, needs to adhere to the level you have set.&lt;/p>
&lt;p>Once you have a baseline set up at your level, you can fix errors in whatever time frame is required. You could choose
to fix errors when you touch a file, or maybe take a bit of time every sprint to fix some errors. Depending on how far along
you are, you can even bump your level all the way to max level and make a bigger baseline. This will make all your newer code
much more type safe, without having to fix all the errors at once.&lt;/p>
&lt;h2 id="getting-your-team-onboard">Getting your team onboard&lt;/h2>
&lt;p>Usually the biggest problem in adding a tool like PHPStan isn&amp;rsquo;t adding the tool itself, it&amp;rsquo;s getting your team onboard
with these new demands your tool makes. This is why you can&amp;rsquo;t just add it at max level with a massive baseline and expect
all your colleagues to completely change the way they write code. Instead, you&amp;rsquo;re going to have to phase it in step by step.&lt;/p>
&lt;p>I like to start by adding PHPStan at level 0 or 1 and having it run in CI. This will get your coworkers to get used to the tool
occasionally telling them about a big error they made. While this is running you can show them how to run PHPStan for just a specific file,
or even for only the changed files in an MR, at a bit of a higher level to catch even more errors. When you feel like they
are becoming more familiar with the tool, you can bump it again in CI and fix more issues.&lt;/p>
&lt;p>Running PHPStan on only changed files at another level is as easy as running:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">vendor/bin/phpstan a &lt;span class="k">$(&lt;/span>git diff --name-only&lt;span class="k">)&lt;/span> --level &amp;lt;desired level&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Depending on how the adoption in your team goes, you may even have other colleagues asking to increase the PHPStan level,
so they can find more errors. And once you&amp;rsquo;re at a suitably high level you could even consider &lt;a href="https://backendtea.com/post/use-phpstan-bleeding-edge/">running PHPStan on bleeding edge&lt;/a>.
By doing so you are becoming forward compatible with new major versions of PHPStan, and finding even more errors
that aren&amp;rsquo;t the default yet.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>If you are introducing PHPStan to your toolkit, be sure to go slow and steady, fix the errors at a lower level, and
slowly increase that over time. Do this until your team has adopted it, and knows how to work with it. You can show
your colleagues how to properly use it, and show them the advantages of PHPStan, to increase the speed at which the adoption happens.&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>What ive been reading: Deep work by Cal Newport</title><link>https://backendtea.com/post/what-ive-been-reading-deep-work/</link><pubDate>Wed, 26 Jun 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/what-ive-been-reading-deep-work/</guid><description>&lt;h2 id="deep-work">Deep work&lt;/h2>
&lt;p>I finished reading deep work about a week ago. If you want to become a mythical 10x developer (or 10x anything really),
this book provides you with the tool on how to succeed in just that.&lt;/p>
&lt;p>A lot of us fall into a trap of doing mostly, what this book calls, shallow work. We have meetings that aren&amp;rsquo;t really
important, we do meaningless tasks, and spend half our day in meeting that could&amp;rsquo;ve been avoided. The book first focuses on why
deep work is important, and why so many of us are doing so little of it. It shows why your organization can even
stop you from performing meaningful deep work, and how to start dong more deep work.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="what-is-deep-work">What is deep work&lt;/h2>
&lt;p>Personally I would define deep work as deeply &lt;strong>focused&lt;/strong> work. It is the ability to focus deeply on something for
longer periods of time, without interruptions. We&amp;rsquo;ve all had work days when every time you are getting into the groove,
something pops up, and you have lost your focus. Whether it&amp;rsquo;s a colleague with a question, an email that needs an immediate response,
or something else entirely. Every time we lose this focus, we need a lot of time to get it back again. Only to have go into
the next meeting.&lt;/p>
&lt;p>Deep work is about saying no to certain requests, not going to the meetings that aren&amp;rsquo;t needed, and focusing on doing
the work that you actually want/need to do. The book argues that most emails don&amp;rsquo;t need a response within a minute and most
questions on slack don&amp;rsquo;t need to be responded to right away.&lt;/p>
&lt;h2 id="deep-work-as-a-developer">Deep work as a developer&lt;/h2>
&lt;p>Deep work as a developer means increasing your productivity by a lot. Your first order of business would be to &lt;strong>not&lt;/strong>
always be available for questions. It is okay to set aside chunks of time to focus on your work, without checking slack,
email or your phone every minute. Of course, you can&amp;rsquo;t just start doing this right away, and you&amp;rsquo;ll need to make sure
your team knows you can&amp;rsquo;t be reached at every minute of the day like before. The book also goes into detail on how to
set this up for yourself.&lt;/p>
&lt;p>Deep work is now sitting on my bookshelf, but I&amp;rsquo;m sure it&amp;rsquo;s a book that I open up again from time to time to remember
how to deal with certain types of shallow work, and how to become more efficient with my time. So I would definitely recommend this book to any developer looking to improve their career.
You can buy deep work on &lt;a href="https://amzn.to/4bnzTKV" target="_blank" rel="noopener">amazon&lt;/a>.&lt;/p>
&lt;!-- https://amzn.to/4bnzTKV -->
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>5 Tips for better code reviews</title><link>https://backendtea.com/post/code-review-tips/</link><pubDate>Fri, 21 Jun 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/code-review-tips/</guid><description>&lt;p>A big part of software engineering is code review. Good code reviews will help your code base become better
and help you and your colleagues learn from each other. Let&amp;rsquo;s take a look at some code review best practices and tips!&lt;/p>
&lt;h2 id="1-have-your-tools-check-the-code-for-you">1: Have your tools check the code for you&lt;/h2>
&lt;p>The best way to have good code review is to reduce what needs to be reviewed as much as possible.
When reviewing code, I shouldn&amp;rsquo;t have to check whether you&amp;rsquo;re indenting with tabs or spaces, a tool should do that.
I shouldn&amp;rsquo;t have to check if the tests pass, that should be done in CI.&lt;/p>
&lt;p>This doesn&amp;rsquo;t mean we need a passing CI before we can do code review.
Sometimes we know there is an error happening, but
we are looking for some early input to see if we are heading in the right direction.&lt;/p>
&lt;h2 id="2-ask-questions-instead-of-demanding-changes">2: Ask questions instead of demanding changes&lt;/h2>
&lt;p>This is especially important when dealing with junior developers, who might not be as confident about their code as more
senior developers. For example, if an input field could be of type &lt;code>email&lt;/code> you should not just say &lt;code>Hey, this can be type=&amp;quot;email&amp;quot;&lt;/code>.
Instead, ask the other developer why they chose for type &lt;code>text&lt;/code>. It might be that legacy users don&amp;rsquo;t have email addresses, but usernames.
Or you might be right and it could be of type &lt;code>email&lt;/code>, in which case they can change it.
But if you just demand a change to be made, the dev might just think you know better and change it without pushing back.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="3-all-devs-should-code-review">3: All devs should code review&lt;/h2>
&lt;p>Some folks think that only seniors should do the code review, but I always encourage junior developers
to do code review as well. It&amp;rsquo;s a great way to learn about the codebase, and about the decisions someone made while coding.
It does mean that seniors will have to spend a bit of extra time to answer the question that may pop up during the review, but
it will increase the skills of the juniors on your team a lot.&lt;/p>
&lt;h2 id="4-review-things-that-matter">4: Review things that matter&lt;/h2>
&lt;p>As developers, we sometimes get stuck on small unimportant things, and we can spend three days arguing whether
a variable should be called &lt;code>$password&lt;/code> or &lt;code>$userPassword&lt;/code>. Try to avoid these kinds of things while doing code review.
Instead, focus on the bigger things in a code review, look at bottlenecks that may cause performance issues, and make
sure the code will be readable by the next person to look at it.&lt;/p>
&lt;h2 id="5-use-fti-or-nit">5: Use FTI or NIT&lt;/h2>
&lt;p>If something is more of an off-hand comment, or doesn&amp;rsquo;t &lt;strong>need&lt;/strong> to be changed, you can use &lt;code>FTI:&lt;/code> or &lt;code>NIT:&lt;/code> in front of your comment,
which stand for &lt;code>Free To Ignore&lt;/code> and &lt;code>Nitpick&lt;/code> respectively. You can use this for comments that don&amp;rsquo;t matter that much, and you could see as nice to haves.
Maybe someone is refactoring a legacy class, but didn&amp;rsquo;t add types everywhere.
This isn&amp;rsquo;t a dealbreaker since we are already doing some &lt;a href="https://backendtea.com/post/boyscout-rule">boy scouting&lt;/a>, but more improvements &lt;em>could&lt;/em> be made.&lt;/p>
&lt;p>In that case you could leave a comment like: &amp;ldquo;FTI: There are still some types missing on the methods, maybe we can add those as well?&amp;rdquo;
Because of the &lt;code>FTI:&lt;/code>, the developer knows they don&amp;rsquo;t &lt;strong>have&lt;/strong> to do this. Generally speaking no changes &lt;strong>need&lt;/strong> to be made,
if you have a reason why the current solution is better than what the reviewer is asking. But with &lt;code>FTI:&lt;/code>, you give the developer
permission to ignore it, or just not make the change without having to reply.&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Analyze your tests with PHPStan</title><link>https://backendtea.com/post/phpstan-analyse-your-tests/</link><pubDate>Mon, 10 Jun 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/phpstan-analyse-your-tests/</guid><description>&lt;p>Hopefully you are already using &lt;a href="https://phpstan.org/" target="_blank" rel="noopener">PHPStan&lt;/a> to analyze your source code.
But you shouldn&amp;rsquo;t stop there, in fact, you should also make sure you are analyzing your test code.&lt;/p>
&lt;h2 id="why-analyze-your-tests">Why analyze your tests&lt;/h2>
&lt;p>If you are already using PHPStan to analyze your source code, then you probably see the value in
static analysis. The same reasons for analyzing your source code are true for your tests.
You can find bugs without running your code, and it tells you of potential errors.&lt;/p>
&lt;p>There is one difference between your test and source code however. Which is that your test code is (probably)
always executed in your CI. Meaning that if an error is guaranteed to occur you would notice it while running your tests.
But if you have a larger project where the tests may take a long time, the static analysis might be faster.&lt;/p>
&lt;p>There are more reasons to analyze your tests though. PHPStan will also help you find assertions to make your tests more clear,
and detect assertions that are always true.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="useful-extensions">Useful extensions&lt;/h2>
&lt;p>The best extension to use if you are analyzing your tests is the &lt;a href="https://github.com/phpstan/phpstan-phpunit" target="_blank" rel="noopener">PHPstan PHPUnit extension&lt;/a>.&lt;/p>
&lt;p>This will detect that the following assertion is useless, as the value is always true. This is of course a simplified example, but I&amp;rsquo;ve
seen conditions like these in my tests before.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">true&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Another great feature of the plugin is that it will tell you if you can use more specific assertions. &lt;a href="https://backendtea.com/post/phpunit-your-first-tests/#phpunit-assertions">You can read why that is usefull right here.&lt;/a>&lt;/p>
&lt;p>So for example it will tell you to make the following change:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- $this-&amp;gt;assertSame(true, $var);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ $this-&amp;gt;assertTrue($var);
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>If you aren&amp;rsquo;t analyzing your tests yet, start doing so today. Simply add your &lt;code>tests&lt;/code> folder to your PHPStan config and start today.
You can either add all your errors to the baseline, and fix them at a later point, or perhaps fix them today.&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>How to create a random string in PHP</title><link>https://backendtea.com/post/php-random/</link><pubDate>Tue, 04 Jun 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/php-random/</guid><description>&lt;p>There are a lot of ways to generate a random string in PHP. In this post we&amp;rsquo;ll take a look at two options we have, and how to use them.&lt;/p>
&lt;h2 id="the-randomizer">The Randomizer&lt;/h2>
&lt;p>The Randomizer class (introduced in PHP 8.2) is arguably the best way in OOP PHP to use randomness, and should be used whenever possible.
It comes with 2 methods for creating random strings. Where &lt;code>getBytes&lt;/code> will give you a random set of bytes of the specified length, but
characters may not be printable, or valid UTF-8. The second one, is the one you&amp;rsquo;ll likely use most of the time, which allows you to specify what
characters you want in your output string. If you specify a character multiple times in the &lt;code>$string&lt;/code> variable, it is more likely to occur.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Introduced in PHP 8.2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">public&lt;/span> &lt;span class="nx">getBytes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$length&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Introduced in PHP 8.3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">public&lt;/span> &lt;span class="nx">getBytesFromString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$string&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$length&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h3 id="how-to-use-the-randomizer">How to use the Randomizer&lt;/h3>
&lt;p>The randomizer class takes an &lt;code>Engine&lt;/code> interface as an optional argument. If not provided, PHP will use the &lt;code>Secure&lt;/code> variant.
So lets write a class that generates a random subdomain for a website.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">Random\Randomizer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">SubDomainBuilder&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="no">KEY_SPACE&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;abcdefghijklmnopqrstuvwxyz0123456789&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">Randomizer&lt;/span> &lt;span class="nv">$randomizer&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">createsubDomain&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$domain&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;%s.%s&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">randomizer&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getBytesFromString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">KEY_SPACE&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">8&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$domain&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$builder&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">SubDomainBuilder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">Randomizer&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">echo&lt;/span> &lt;span class="nv">$builder&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">createsubDomain&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;example.com&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In this &lt;code>SubDomainBuilder&lt;/code> class, we have the &lt;code>createsubDomain&lt;/code> method, where we use the &lt;code>getBytesFromString&lt;/code> method of the &lt;code>Ranomizer&lt;/code>.
We pass it a keyspace of a through z, and the numbers 0 to 9, so we get a sub domain which is only lowercased letters and numbers.
Running this script will output something simmilar to &lt;code>x0elme6k.example.com&lt;/code>.&lt;/p>
&lt;p>One of the great things about the &lt;code>Randomizer&lt;/code> class is that you can provide an engine, and can seed that engine. This will make the
results deterministic, and allow you to use the randomness in unit tests.&lt;/p>
&lt;p>For example, if we change the using of the class to the following, we&amp;rsquo;ll get the same results every single time. (&lt;code>nleajcf2.example.com&lt;/code>).
This means that we can use a seeded Engine in our unit tests, to make sure we always get the same results.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$random&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Randomizer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">Mt19937&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">123&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$builder&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">SubDomainBuilder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$random&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">echo&lt;/span> &lt;span class="nv">$builder&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">createsubDomain&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;example.com&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="creating-a-random-string-before-php-82">Creating a random string before PHP 8.2&lt;/h2>
&lt;p>If you are on PHP 8.2 or higher you should use the &lt;code>Randomizer&lt;/code>. However, if you&amp;rsquo;re stuck on an earlier version of PHP, and want to generate a random
string, you can create your own Random generateor which uses the &lt;code>random_int&lt;/code> function as follows to generate your own random strings.
The class would roughly look as follows. You could even define often used key spaces in this class to re-use.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">RandomGenerator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getBytesFromString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$keySpace&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$length&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$keySpaceLength&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">strlen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$keySpace&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$randomString&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nv">$i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nv">$length&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nv">$i&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$randomString&lt;/span> &lt;span class="o">.=&lt;/span> &lt;span class="nv">$keySpace&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">random_int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$keySpaceLength&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$randomString&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In newer PHP versions is has become surprisingly easy to generate random strings. If youre on version 8.2 or higher, the
randomizer is the way to go. If youre a bit earlier than that, you can still use the &lt;code>random_int&lt;/code> function to create safe random strings.&lt;/p>
&lt;p>If you want to get notified of my next blog post, join the newsletter&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Mastering PHPUnit: Using Mocks and Stubs</title><link>https://backendtea.com/post/phpunit-mock-and-stub/</link><pubDate>Thu, 30 May 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/phpunit-mock-and-stub/</guid><description>&lt;p>This is part 4 of a series on PHPUnit testing, you can find part 3 &lt;a href="https://backendtea.com/post/phpunit-data-providers/">here&lt;/a>.
In this post we&amp;rsquo;ll focus on the use of mocks. We&amp;rsquo;ll cover why we need them, the different types of mocks, and how to use them.&lt;/p>
&lt;h2 id="why-do-we-need-mocks">Why do we need mocks?&lt;/h2>
&lt;p>A mock is a stand in for a dependency that you dont want to (currently) test. The most commonly used example might be the database.
In your unit tests, you don&amp;rsquo;t want to actually access the database. Instead you want to &lt;em>mock&lt;/em> the usage of the database, so that you
can test your logic without it. The two reasons for this are speed and isolation. To start with speed, unit tests need to be fast.
If you unit test takes more then a few miliseconds it is slow. (Don&amp;rsquo;t take the startup time of PHPUnit into account for this.)
In a bigger project you&amp;rsquo;ll likely have thousands if not tens of thousands of unit tests. If each of them would have to talk to the database, and then
reset it afterwards, your test suite would take hours.&lt;/p>
&lt;p>Another reason to have mocks is to test your code in isolation. Generally we want our unit tests to test one thing. So we dont&amp;rsquo; really care what all
the other classes are doing under the hood. Those have their own unit tests to cover that behaviour. Take the next bit of code for example.
We fetch some data, and make sure the &lt;code>status&lt;/code> is &lt;code>OK&lt;/code>. In a unit test we dont want to use the &lt;code>HttpFetcher&lt;/code> class, instead we&amp;rsquo;ll mock that, and only
test the checking of status.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BuildData&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">HttpFetcher&lt;/span> &lt;span class="nv">$fetcher&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">build&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">fetcher&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fetch&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$data&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;status&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="s1">&amp;#39;OK&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Exception&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="nx">Status&lt;/span> &lt;span class="nx">was&lt;/span> &lt;span class="k">not&lt;/span> &lt;span class="nx">OK&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="different-types-of-mocks">Different types of mocks&lt;/h2>
&lt;p>I&amp;rsquo;ve been talking about mocks so far, but in PHP we can differentiate 2 types, which are &lt;strong>Mocks&lt;/strong> and &lt;strong>Stubs&lt;/strong>.
The difference lies in what we do with them. On a stub we can configure behaviour. We can tell it what to return on
a certain method call for example. On a mock we can also do this, but in addition to that we can also configure expecations.
So we can make sure the method gets called twice, or with certain parameters.&lt;/p>
&lt;p>If the &amp;lsquo;side effect&amp;rsquo; of your method is what you are interested in, then you want to test that using mocks. Otherwise you should use a stub.
For example, if you have a method which needs to make an API call with very specific parameters,
then you can use a mock to test that that call was made with the right parameters.
But if you have a method which needs to get some data from a dependency, then you should just use a stub.&lt;/p>
&lt;p>In general i would always use a stub, unless its absolutely necesarry to use a mock. Which would only be if you really need to test
that a certain call is made.&lt;/p>
&lt;h2 id="how-to-use-mocks">How to use mocks&lt;/h2>
&lt;p>Lets try to write a test for the &lt;code>BuildData&lt;/code> class that we wrote a little earlier.
Without mocks it would look something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BuildDataTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItReturnsData&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$buildData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">BuildData&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">HttpFetcher&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$buildData&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">build&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s1">&amp;#39;status&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;OK&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now this test is gonna fail. The htpt server can&amp;rsquo;t be reaced, or maybe we need some more set up for that. But we don&amp;rsquo;t need to talk to the
external server, if we use a stub for this. We can create a stub in PHPUnit with &lt;code>$this-&amp;gt;createStub(&amp;lt;class_name&amp;gt;)&lt;/code>. So lets do that:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BuildDataTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItReturnsData&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$fetcher&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">createStub&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">HttpFetcher&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$fetcher&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">method&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;fetch&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">willReturn&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s1">&amp;#39;status&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;OK&amp;#39;&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$buildData&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">BuildData&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$fetcher&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$buildData&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">build&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s1">&amp;#39;status&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;OK&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now this test will pass. We use the &lt;code>willReturn&lt;/code> to configure our stub to return the correct data. (in this case the &lt;code>['status' =&amp;gt; 'OK']&lt;/code>)
Since here it is not that important how many times something is called, or what it is called with, we dont need to use a mock, and we can use a stub.
One example where the amount of times something is called, or with what it is called does become important is with cached calls.
For example in the following &lt;a href="https://backendtea.com/post/every-day-design-pattern-decorator/">adapter&lt;/a>, we want to test that we only call our api once per id.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CachedApi&lt;/span> &lt;span class="k">implements&lt;/span> &lt;span class="nx">Api&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sd">/** @var array&amp;lt;int, string&amp;gt; */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="nv">$cache&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">Api&lt;/span> &lt;span class="nv">$innerApi&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">fetchApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">cache&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nv">$id&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">??=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">innerApi&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fetchApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So for this class we could write the following tests, where we use &lt;code>createMock&lt;/code> instead of &lt;code>createStub&lt;/code>.
We now also use the &lt;code>expects&lt;/code> to configure how often something should be called. (You can also use &lt;code>$this-&amp;gt;never()&lt;/code> to configure if something should never be called).
And in the first test method we use &lt;code>with&lt;/code>, to configure what the method should be called with, since it is relatively important.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BuildDataTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItCallsInnerApiOnce&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$innerApi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">createMock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Api&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$innerApi&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">expects&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">once&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">method&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;fetchApi&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">123&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">willReturn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;foo&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cachedApi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">CachedApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$innerApi&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cachedApi&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fetchApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">123&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cachedApi&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fetchApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">123&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItCallsInnerForEachId&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$innerApi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">createMock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Api&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$innerApi&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">expects&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">exactly&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">method&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;fetchApi&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">willReturn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;foo&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cachedApi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">CachedApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$innerApi&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cachedApi&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fetchApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">123&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cachedApi&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fetchApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">456&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cachedApi&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fetchApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">456&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cachedApi&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fetchApi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">123&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Mastering PHPUnit: Using data providers</title><link>https://backendtea.com/post/phpunit-data-providers/</link><pubDate>Wed, 22 May 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/phpunit-data-providers/</guid><description>&lt;p>This is part 3 of a series on PHPUnit testing, you can find part 2 &lt;a href="https://backendtea.com/post/phpunit-your-first-tests/">here&lt;/a>.
In this post we&amp;rsquo;ll focus on the use of data providers. I have written about them before, but for earlier
PHPUnit versions. This post is relevant for PHPUnit version 10 or higher. For version 9 or lower
&lt;a href="https://backendtea.com/post/phpunit-beyond-basic-dataproviders/">check out the original data provider post&lt;/a>&lt;/p>
&lt;h2 id="what-are-data-providers">What are data providers?&lt;/h2>
&lt;p>A data provider is a function or method that, as the name suggests, provides data to your tests.
So instead of having to write a new test case for every option, we write a single test, and then
let it run multiple times with a lot of data, to make sure it handles all cases as expected.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="creating-data-providers">Creating data providers.&lt;/h2>
&lt;p>A data provider is a &lt;strong>static&lt;/strong> method which provides input data for our tests.
This method either lives in your test case itself, or in another class.
Generally we want to keep the data provider in the same class, so we keep the data of our test close
to the test itself. You&amp;rsquo;d only move it to another class if the test data is re-used among multiple tests.&lt;/p>
&lt;p>For this example we&amp;rsquo;ll test a &amp;lsquo;calculator&amp;rsquo; class which adds two numbers, to show you how that works.
So let&amp;rsquo;s create that class and test case class as discussed in &lt;a href="https://backendtea.com/post/phpunit-your-first-tests/">the previous post&lt;/a>.
And there we add our test method like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//Calculator.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="nx">App&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Calculator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$one&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//CalculatorTest.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="nx">test&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">App\Calculator&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">PHPUnit\Framework\TestCase&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CalculatorTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItAdds&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$calculator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Calculator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$calculator&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now this is only one case. But we need to deal with negative numbers, zero, etc.
We could write out a test case for each of these. But instead, lets create a data provider, to give us this test data.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="nx">test&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">App\Calculator&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">Generator&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">PHPUnit\Framework\Attributes\DataProvider&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">PHPUnit\Framework\TestCase&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CalculatorTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">#[DataProvider(&amp;#39;provideAddCases&amp;#39;)]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItAdds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$expected&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$calculator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Calculator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$expected&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$calculator&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideAddCases&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Generator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see we added the &lt;code>DataProvider&lt;/code> attribute to our test method, and gave it input parameters.
The &lt;code>DataProvider&lt;/code> attribute takes as an argument the method name on the current test class that
we are using. The data from the array that we &lt;code>yield&lt;/code> is passed to the test method. So in our example, the dataprovider
gives us &lt;code>$one = 1&lt;/code>, &lt;code>$two = 2&lt;/code>, &lt;code>$expected = 3&lt;/code>.
(From PHPUnit 11.1 you can also use string keys for the array, which are passed to the argument with the correct name.)&lt;/p>
&lt;p>Now if we want to add extra test cases, all we have to do is expand our data provider. So lets add some:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="nx">test&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">App\Calculator&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">Generator&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">PHPUnit\Framework\Attributes\DataProvider&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">PHPUnit\Framework\TestCase&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CalculatorTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">#[DataProvider(&amp;#39;provideAddCases&amp;#39;)]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItAdds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$expected&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$calculator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Calculator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$expected&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$calculator&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideAddCases&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Generator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">9&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we now run our tests, and look at the bottom of the screen we&amp;rsquo;ll see the following. This means we
ran 6 tests, while we only had to write one.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">OK (6 tests, 6 assertions)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="why-not-test-in-a-loop">Why not test in a loop?&lt;/h2>
&lt;p>Now you may be thinking, why should I use a data provider for this? After all you could just write a foreach loop
to go over that data, and run the assertion for each option.
The downside of this, is that if one of the checks fail it stops executing right after that.
So if our case of &lt;code>yield [-12,7,-5];&lt;/code> failed to work, we would not execute the 2 test cases after that. Meaning we need to
fix the first bug, before we can even see if the other test cases are passing. This can be especially annoying when
refactoring code, and introducing a bug. Now it becomes harder to spot if you introduced more bugs.&lt;/p>
&lt;p>Another upside of these data providers is that you can add multiple data provider to one method.
Now for this example it may not be needed, but you may want to split your providers up if you have
a lot of test cases. So let&amp;rsquo;s say that for our calculator test we want to split up the data of negative
and positive numbers, we could easily do it like this, and still not have to write a second test.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CalculatorTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">#[DataProvider(&amp;#39;provideAddCases&amp;#39;)]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">#[DataProvider(&amp;#39;provideNegativeAddCases&amp;#39;)]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItAdds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$expected&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$calculator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Calculator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$expected&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$calculator&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideAddCases&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Generator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideNegativeAddCases&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Generator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">9&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Data providers will also work better if you are tying to test with exceptions.
I explain more about that in an earlier blog post about &lt;a href="https://backendtea.com/post/phpunit-exception-test/">testing exceptions with PHPUnit&lt;/a>.&lt;/p>
&lt;h2 id="extra-tips-for-testing-with-data-providers">Extra tips for testing with data providers&lt;/h2>
&lt;p>Data providers are great for quickly finding edge cases of the methods you are testing.
So if you are testing something that takes an array, try testing with an empty one, with one value, with two, and maybe 5 or so.
If you are testing with strings, try inserting an empty string, or maybe a really large one.&lt;/p>
&lt;p>Data providers can also help you with recreating real life scenarios. For example, I had a bug
report once of prices on our website displaying weirdly if the price was above 10.0000.
Which hadn&amp;rsquo;t really happened because most products were under 30 bucks, but it does happen in some
currencies which are a lot bigger.&lt;/p>
&lt;p>Now since this code was already tested, all I had to do was add a case where I added some really
large numbers, and the bug was reproduced right away. And because the code was very well tested, the
change was easy to make without breaking anything for smaller numbers.&lt;/p>
&lt;p>Next weeks post will discuss &lt;code>Mocking and Stubbing&lt;/code> in PHPUnit, which will help us deal with dependencies
we don&amp;rsquo;t want to use while running our tests. You can find that post &lt;a href="https://backendtea.com/post/phpunit-mock-and-stub/">right here&lt;/a>.&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Mastering PHPUnit: Writing Your First Test with PHPUnit</title><link>https://backendtea.com/post/phpunit-your-first-tests/</link><pubDate>Fri, 17 May 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/phpunit-your-first-tests/</guid><description>&lt;p>This is part 2 of a series on PHPUnit testing, you can find part 1 &lt;a href="https://backendtea.com/post/phpunit-introduction/">here&lt;/a>. This post
will have us writing our first unit test, and explain some of the basics in doing so.&lt;/p>
&lt;h2 id="testcase-class">TestCase class&lt;/h2>
&lt;p>Every test we write has to extend the &lt;code>PHPUnit\Framework\TestCase&lt;/code> class.
PHPUnit will look for classes in its configured folders that extend from this class, to execute tests.
Within your class it will look for all methods that have the &lt;code>PHPUnit\Framework\Attributes\Test&lt;/code> attribute (&lt;code>#[Test]&lt;/code>),
or where the name of the method starts with &lt;code>test.&lt;/code> If either of those is true, PHPUnit will consider it a test, and run
it as such.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="phpunit-assertions">PHPUnit assertions&lt;/h2>
&lt;p>As mentioned in the &lt;a href="https://backendtea.com/post/phpunit-introduction/">first post of this series&lt;/a>, we want to split up our code in three parts,
arrange, act and assert. Especially for this third step, the &lt;code>assert&lt;/code>, there are a lot of options built right into
PHPUnit. We have a lot of assertions out of the box. To name some:&lt;/p>
&lt;ul>
&lt;li>&lt;code>assertSame&lt;/code>&lt;/li>
&lt;li>&lt;code>assertNotSame&lt;/code>&lt;/li>
&lt;li>&lt;code>assertEquals&lt;/code>&lt;/li>
&lt;li>&lt;code>assertTrue&lt;/code>&lt;/li>
&lt;li>&lt;code>assertFalse&lt;/code>&lt;/li>
&lt;li>&lt;code>assertNull&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>And many more.&lt;/p>
&lt;p>Generally you want to use the most specific assertions. So instead of doing &lt;code>self::assertSame(true, $myValue)&lt;/code>, you
would want to use &lt;code>self::assertTrue($myValue)&lt;/code>. If you are using a tool like &lt;a href="https://phpstan.org" target="_blank" rel="noopener">PHPStan&lt;/a>, you can use
the &lt;a href="https://github.com/phpstan/phpstan-phpunit" target="_blank" rel="noopener">phpunit extension&lt;/a> to automatically detect when you can use these.&lt;/p>
&lt;p>An assert call does what you expect it to, it asserts that something is what you want it to be. With &lt;code>assertTrue&lt;/code>
you assert your value is true. With &lt;code>assertSame&lt;/code> you assert 2 variables are the same, with &lt;code>assertNotSame&lt;/code> you assert
they are not the same.&lt;/p>
&lt;p>What is important to know about these assertions is that they throw a specific PHPUnit exception if they fail, so test
execution stops at that point. So if you take the following code example, the &lt;code>self::assertSame(3,4);&lt;/code> will not be executed.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For a full list of all the possible assertions, you can look at the &lt;a href="https://docs.phpunit.de/en/11.1/assertions.html" target="_blank" rel="noopener">official documentation&lt;/a>&lt;/p>
&lt;h2 id="avoiding-code-duplication-it-tests">Avoiding code duplication it tests&lt;/h2>
&lt;p>A lot of the time, your tests will share most of their setup.
In case of more legacy projects that need a lot of setup to run, you might want to
extract that. To facilitate that, you can override the following methods in your test class:&lt;/p>
&lt;ul>
&lt;li>&lt;code>setUp&lt;/code>&lt;/li>
&lt;li>&lt;code>tearDown&lt;/code>&lt;/li>
&lt;li>&lt;code>setUpBeforeClass&lt;/code> (static)&lt;/li>
&lt;li>&lt;code>tearDownAfterClass&lt;/code> (static)&lt;/li>
&lt;/ul>
&lt;p>The first method to be called is &lt;code>setUpBeforeClass&lt;/code> this is a static method that
is called before any test are ran.
Next the &lt;code>setUp&lt;/code> is called, which is called before every test itself, so if you have 4 tests
in your class, &lt;code>setUp&lt;/code> will be called 4 times, just before each test is started.&lt;/p>
&lt;p>After the test is finished &lt;code>tearDown&lt;/code> is called, in the same matter as &lt;code>setUp&lt;/code>, it&amp;rsquo;s called every time a test is finished.
And lastly, once all tests fo the class are finished, &lt;code>tearDownAfterClass&lt;/code> is called.&lt;/p>
&lt;p>If you are familiar with a javascript framework like Jest, the &lt;code>setUp&lt;/code> and &lt;code>tearDown&lt;/code> are the PHPUnit equivalent of &lt;code>beforeEach&lt;/code> and &lt;code>afterEach&lt;/code>.&lt;/p>
&lt;h2 id="writing-a-test-with-setup-and-teardown">Writing a test with setUp and tearDown&lt;/h2>
&lt;p>Let&amp;rsquo;s say we have some code that looks like this. I know it doesn&amp;rsquo;t look good, but it is common in older applications to have
global variables that are manipulated.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$globalVariable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">globalManipulation&lt;/span> &lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="nv">$globalVariable&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$globalVariable&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$globalVariable&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Let&amp;rsquo;s write some tests for this code. We&amp;rsquo;ll have one method where we call it once, (and then it should be 1).
And then another method where we call it twice,and it should be two.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestGlobalFunction&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItIsOne&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">globalManipulation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItIsTwo&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">globalManipulation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">globalManipulation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now if we run the test we get an output which tells us we have a failure.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">There was 1 failure:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1) tests\TestGlobalFunction::testItIsTwo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Failed asserting that 3 is identical to 2.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The reason that the value is 3, and not 2,
is because we haven&amp;rsquo;t reset the &lt;code>$globalVariable&lt;/code>, so let&amp;rsquo;s implement &lt;code>setUp&lt;/code> and &lt;code>tearDown&lt;/code> to reset the variable each time.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">TestGlobalFunction&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">protected&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">setUp&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="nv">$globalVariable&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$globalVariable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">protected&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">tearDown&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">global&lt;/span> &lt;span class="nv">$globalVariable&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$globalVariable&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItIsOne&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">globalManipulation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItIsTwo&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">globalManipulation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">globalManipulation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$value&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now as you can see we added the logic to both &lt;code>setUp&lt;/code> and &lt;code>tearDown&lt;/code>. In the &lt;code>setUp&lt;/code> we change it so we know our tests
start with a predictable state. In the &lt;code>tearDown&lt;/code> we set it, so we know our test don&amp;rsquo;t influence other tests.&lt;/p>
&lt;p>In &lt;a href="https://backendtea.com/post/phpunit-data-providers/">next weeks post&lt;/a> we&amp;rsquo;ll discuss &lt;code>Data Providers&lt;/code> in PHPUnit, which will help us write a lot of test cases
in a short time, and reduce code duplication.
If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Mastering PHPUnit: an introduction</title><link>https://backendtea.com/post/phpunit-introduction/</link><pubDate>Fri, 03 May 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/phpunit-introduction/</guid><description>&lt;p>In this blog series we&amp;rsquo;ll go from writing our first unit test to being a PHPUnit master.
This first post will go over the basics, and introduce you to PHPUnit&lt;/p>
&lt;h2 id="what-is-phpunit">What is PHPUnit?&lt;/h2>
&lt;p>PHPUnit is the de facto testing framework for PHP.
It initially started in 2001, over 23 years ago at this point. And even today it is still under active development.
All its development is done on
, where you can open issues if you
have a feature request, or find a bug.&lt;/p>
&lt;h2 id="why-do-we-test">Why do we test?&lt;/h2>
&lt;p>There is a lot of talk about whether you should even write unit tests.
To summarize my thoughts on the matter: You should.
I don&amp;rsquo;t trust myself (or anyone else) to write perfect code. Which is why we need to validate
that our code works. Now we could manually verify every bit of code we write. For web development
we could reload a page, click a few buttons, and consider our code working.
But, for every change we make we&amp;rsquo;ll have to do that work again.&lt;/p>
&lt;p>With unit testing we validate this automatically. We write a test that checks that our code
does what we think it does. Now instead of having to perform all the possible actions of our code
every time we refactor, we simply run our unit tests, and we know in a matter of milliseconds if our
code works.&lt;/p>
&lt;p>The &amp;lsquo;downside&amp;rsquo; of unit testing is that we have to write these tests, which means we have to write more
code. It also means that on top of code we need to maintain our unit tests as well. Which means dependency updates,
refactors etc. In my opinion the investment in unit tests outweighs the costs.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="aaa-the-three-steps-to-every-unit-test">AAA: The three steps to every unit test&lt;/h2>
&lt;p>In general, we can split up our tests in three parts: &lt;strong>arrange, act, assert&lt;/strong>.&lt;/p>
&lt;h3 id="arrange">Arrange&lt;/h3>
&lt;p>In the arrange phase we do all the set up to make sure that we can run the code that is needed.&lt;/p>
&lt;p>We may need to set up some mocks (more about those in a future post), set up the environment or do
other things.&lt;/p>
&lt;h3 id="act">Act&lt;/h3>
&lt;p>In the act phase we run the actual code that we want to execute. In unit testing this is usually
just one line of code, where we run the code.&lt;/p>
&lt;h3 id="assert">Assert&lt;/h3>
&lt;p>After we run the code that we want to test, we of course need to check that the code did what we
want to do. So we &lt;code>assert&lt;/code> that it did what we expected it to do. Here we&amp;rsquo;ll check either the output
of the method test, or check for the side effects that the method caused.&lt;/p>
&lt;h2 id="adding-a-test-to-a-project">Adding a test to a project&lt;/h2>
&lt;p>Let&amp;rsquo;s assume we have a project with the following structure. For this i&amp;rsquo;ll assume you&amp;rsquo;ve worked with
composer before and have autoloading etc. set up.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">📦 project
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┣ 📂 src
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┃ ┗ 📜 Calculator.php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┣ 📜 composer.json
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With the &lt;code>Calculator.php&lt;/code> looking like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">declare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strict_types&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="nx">App&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Calculator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$one&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>First off we&amp;rsquo;ll run &lt;code>composer require --dev phpunit/phpunit&lt;/code>, to install phpunit as a dev dependency.&lt;/p>
&lt;p>Then we&amp;rsquo;ll create a file called &lt;code>phpunit.xml&lt;/code> to configure phpunit with. (We&amp;rsquo;ll go into detail about this config file in a future post.)&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;phpunit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xsi:noNamespaceSchemaLocation=&lt;/span>&lt;span class="s">&amp;#34;./vendor/phpunit/phpunit/phpunit.xsd&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;testsuites&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;testsuite&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;AllTests&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;directory&amp;gt;&lt;/span>test/&lt;span class="nt">&amp;lt;/directory&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/testsuite&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/testsuites&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/phpunit&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then we&amp;rsquo;ll continue by creating a file called &lt;code>CalculatorTest.php&lt;/code> in the &lt;code>test&lt;/code> folder we&amp;rsquo;ll create.&lt;/p>
&lt;p>So now our project will look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">📦 project
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┣ 📂 src
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┃ ┗ 📜 Calculator.php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┣ 📂 test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┃ ┗ 📜 CalculatorTest.php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┣ 📜 composer.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┣ 📜 phpunit.xml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now we&amp;rsquo;ll write our first test. For now just an empty test, which doesn&amp;rsquo;t do anything.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">declare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strict_types&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="nx">App\Test&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">App\Calculator&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">PHPUnit\Framework\TestCase&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CalculatorTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testAdd&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we now run phpunit, we&amp;rsquo;ll get an output like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">$ vendor/bin/phpunit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PHPUnit 11.1.3 by Sebastian Bergmann and contributors.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Runtime: PHP 8.3.6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Configuration: /Volumes/SourceCode/Private/hugo-blog-test/phpu/phpunit.xml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">R 1 / 1 (100%)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Time: 00:00.007, Memory: 6.00 MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">There was 1 risky test:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1) App\Test\CalculatorTest::testAdd
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">This test did not perform any assertions
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">/Volumes/SourceCode/Private/hugo-blog-test/phpu/test/CalculatorTest.php:12
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OK, but there were issues!
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Tests: 1, Assertions: 0, Risky: 1.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>PHPunit has executed our test, but it didn&amp;rsquo;t do any assertions, which is a problem.&lt;/p>
&lt;p>So let actually test something, and fill in the &lt;code>testAdd&lt;/code> method, like so:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testAdd&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// arrange
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nv">$calculator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Calculator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">//act
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nv">$output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$calculator&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">//assert
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$output&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>As you can see we have the three steps, &lt;code>arrange&lt;/code>, &lt;code>act&lt;/code>, &lt;code>assert&lt;/code>. Now in normal development
you wouldn&amp;rsquo;t explicitly add these comments of the different phases, but it&amp;rsquo;s good to keep in mind how
you structure your test.&lt;/p>
&lt;p>Now if we run phpunit we&amp;rsquo;ll get the following output:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">$ vendor/bin/phpunit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">PHPUnit 11.1.3 by Sebastian Bergmann and contributors.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Runtime: PHP 8.3.6
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Configuration: /Volumes/SourceCode/Private/hugo-blog-test/phpu/phpunit.xml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">. 1 / 1 (100%)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Time: 00:00.003, Memory: 6.00 MB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OK (1 test, 1 assertion)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s a success! We have a test, and we made an assertion about our code, now if we update
the calculator class we can run the test again, and validate our code still works as before.&lt;/p>
&lt;p>Next week we&amp;rsquo;ll go over how to determine what to test, how to test it, and some common problems
we can face while testing. If you want to get notified of the
, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Never deploy an untagged dependency again</title><link>https://backendtea.com/post/never-deploy-an-untagged-dependency-again/</link><pubDate>Fri, 26 Apr 2024 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/never-deploy-an-untagged-dependency-again/</guid><description>&lt;p>Today we ran into an issue with a build in one of our projects.
Composer could no longer install, as a dependency was gone.&lt;/p>
&lt;p>When we opened our pipeline logs we saw the following error:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">failed to execute git checkout &amp;#39;&amp;lt;ref&amp;gt;&amp;#39; -
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- &amp;amp;&amp;amp; git reset --hard &amp;#39;&amp;lt;ref&amp;gt;&amp;#39; --
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">fatal: reference is not a tree: &amp;lt;ref&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">It looks like the commit hash is not available in the repository, maybe the
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">commit was removed from the branch? Run &amp;#34;composer update acme/dependency&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">to resolve this.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And in our composer.json we found a dependency like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;require&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;acme/dependency&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;dev-feat-new-things as 4.3.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now this worked fine when it got merged, except that after the merge, in the &lt;code>acme/dependency&lt;/code> package,
the &lt;code>feat-new-things&lt;/code> branch got merged into &lt;code>main&lt;/code>, so it no longer existed. Therefore, composer stopped being able to find it.&lt;/p>
&lt;p>Now while working on a project it&amp;rsquo;s perfectly fine to put a dependency on a dev branch while you are working on that branch for a new feature,
but before releasing it, you want to make sure to actually tag this.
Normally we put a comment on a merge request to remind ourselves to tag the release of the package before merging.
But this time that was forgotten, and the merge request was merged without a tag of the dependency.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="the-fix">The fix&lt;/h2>
&lt;p>In order to fix this we wrote the following PHPUnit test.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">NoDevBranchDependenciesTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testComposerHasNoDevBranchDependencies&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$composer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">json_decode&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">file_get_contents&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">__DIR__&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s1">&amp;#39;/../../composer.json&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">?:&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flags&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">JSON_THROW_ON_ERROR&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertIsArray&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$composer&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$requireDev&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$composer&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;require-dev&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">??&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertIsArray&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$requireDev&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$require&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$composer&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;require&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">??&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertIsArray&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$require&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$branchDependencies&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">array_filter&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">array_merge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$requireDev&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$require&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fn&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$version&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">str_starts_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$version&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;dev-&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertEmpty&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$branchDependencies&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Found the following dependencies with a dev key: &amp;#34;%s&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">implode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">array_keys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$branchDependencies&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lets go over it step by step and see what it does:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$composer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">json_decode&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">file_get_contents&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">__DIR__&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s1">&amp;#39;/../../composer.json&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">?:&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flags&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">JSON_THROW_ON_ERROR&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertIsArray&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$composer&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$requireDev&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$composer&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;require-dev&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">??&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertIsArray&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$requireDev&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$require&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$composer&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;require&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">??&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertIsArray&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$require&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This first bit grabs the contents of the &lt;code>composer.json&lt;/code>, and decodes it. The lines after that are
grabbing the production and dev dependencies, and does some type checking on this.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$branchDependencies&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">array_filter&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">array_merge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$requireDev&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$require&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">fn&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$version&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nx">str_starts_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$version&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;dev-&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In the next bit we are looping over all the dependencies, and we only keep
the ones that start with &lt;code>dev-&lt;/code>, which is used by composer to point to branches.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertEmpty&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$branchDependencies&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Found the following dependencies with a dev key: &amp;#34;%s&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">implode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;, &amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">array_keys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$branchDependencies&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>After we have filtered out the dependencies, all that is left to do is report if we found any.
And of course just reporting that an array is not empty to a developer isn&amp;rsquo;t going to make much sense
to them. So we provide a nice message to the other developers to tell them what they did wrong.&lt;/p>
&lt;p>By adding this test we make sure that the pipeline stays red until the dependency points to a proper tag. This way it can&amp;rsquo;t be merged, and
we&amp;rsquo;ll never run into an untagged dependency being merged into main again.&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Stop (ab)using allow failure</title><link>https://backendtea.com/post/stop-abusing-allow-failure/</link><pubDate>Sun, 24 Jul 2022 07:35:43 +0200</pubDate><guid>https://backendtea.com/post/stop-abusing-allow-failure/</guid><description>&lt;p>We&amp;rsquo;ve all had our CI fail due to an issue that couldn&amp;rsquo;t be fixed right away.
Usually something like a security issue in a downstream package that can&amp;rsquo;t be updated yet.
This then causes the build to fail, making us unable to merge or deploy.
&lt;code>allow_failure&lt;/code> seems like a great way to solve this, but it is not.&lt;/p>
&lt;p>&lt;code>allow_failure&lt;/code> is setting us up for failure. Because now any issue with this check is ignored.
And even after the vulnerable dependency is updated, the &lt;code>allow_failure&lt;/code> stays. Meaning next time
you won&amp;rsquo;t get an error when there is a vulnerability.&lt;/p>
&lt;p>Instead, allow your MR to be merged even when it is red, and allow the deploy step to be executed,
even if previous steps fail. This should be discussed with the team beforehand, as we don&amp;rsquo;t want to
merge and deploy when a unit test is failing, or if another check is broken. You only want to do this
when you simply can&amp;rsquo;t fix the issue right away.&lt;/p>
&lt;p>In gitlab CI you can use the &lt;code>needs&lt;/code> keyword to specify what steps are exactly needed for in order
to run the current step. So you can do something like this. Where the 2 build steps have no &lt;code>needs&lt;/code>, and
the deploy steps only require the &lt;code>build&lt;/code> steps. Meaning that even if the other steps fail, you can still run
these manual steps.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">tests&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c">#checks&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">security&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c">#checks&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">php-build-release&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">needs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">nodejs-build-release&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">needs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">deploy-test-release&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">deploy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">when&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">manual&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">needs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">php-build-release&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">nodejs-build-release&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># deploy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">deploy-prod-release&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">deploy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">when&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">manual&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">needs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">php-build-release&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">nodejs-build-release&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># deploy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So stop using &lt;code>allow_failure&lt;/code>, as it will stop you from seeing new errors popping up. Instead, allow
your pipeline to be red for a short while. But do make sure your team can handle this responsibility.&lt;/p></description></item><item><title>Creating better services</title><link>https://backendtea.com/post/creating-better-services/</link><pubDate>Sat, 13 Nov 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/creating-better-services/</guid><description>&lt;p>When writing code, you generally want to split up the logic in to different classes.
You have your controller classes which take a request, and return a response.
You write value objects to represent information which is important to your application.
You may write commands, command handlers, repositories and more. The most important group
of classes you write are probably your services, which hold (most) of the logic of your app.&lt;/p>
&lt;p>There are a few rules which will help you write better, more reliable services, so lets go over
them.&lt;/p>
&lt;h2 id="services-dont-need-data-classes-to-be-created">Services dont need data classes to be created&lt;/h2>
&lt;p>A service should not need &amp;lsquo;data&amp;rsquo; in order to be created.
For example, in an e-commerce setting, you may have a service class which deals with
the logic surrounding the cart. This class can &lt;strong>never&lt;/strong> have the current cart as a property,
or require it in the constructor.&lt;/p>
&lt;p>Of course you can still give the cart as an argument to a method, and perform logic on that.
But the class itself should not hold the cart.&lt;/p>
&lt;p>Requiring the cart would mean you need to do a lot of operations, just to create this class.
You would need to know the &amp;lsquo;current&amp;rsquo; cart id, the DB would need to be queried etc. This means
the class becomes a lot harder to create.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="services-dont-have-mutable-state">Services dont have mutable state&lt;/h2>
&lt;p>A service should not have a data class as a property, or any data that can be mutated.
For example, if you have a class which may behave differently in a test environment.
Then whether or not this is a test environment should be injected in the constructor.
You should not have a setter for this behavior.&lt;/p>
&lt;p>When you allow the state of the service to be changed, then the service may have different
behavior every single time, and it becomes unpredictable what happens.&lt;/p>
&lt;p>An exception here is if you have a cache layer in your service. e.g. an array holding previously
calculated results.&lt;/p>
&lt;h2 id="services-dont-depend-on-a-context-class">Services dont depend on a context class&lt;/h2>
&lt;p>A &amp;lsquo;context&amp;rsquo; class is a class that knows about the current context.
This may be a &lt;code>Request&lt;/code> object, or an &lt;code>InputInterface&lt;/code> for the CLI.
Other examples are the symfony &lt;code>Security&lt;/code> class, which knows about the current user.
Instead the data of the current context that this service needs should be passed along
with in a parameter of the method.&lt;/p>
&lt;p>Doing so will allow you to use the service in a different context. For example a class
that requires the &lt;code>InputInterface&lt;/code> object is not usable in a web environment.
And if you give the &lt;code>Security&lt;/code> class to a service you become unable to &amp;lsquo;use&amp;rsquo; another user,
for example in the CLI.&lt;/p>
&lt;h2 id="every-method-should-do-only-1-thing">Every method should do only 1 thing&lt;/h2>
&lt;p>This is true for almost all classes, but is worth stating again for services.&lt;/p>
&lt;p>Instead of a method doing logging, querying the database and handling logic, let your
service do exactly one thing.
Move the database stuff to a repository, and handle the logging with a &lt;a href="https://backendtea.com/post/every-day-design-pattern-decorator/">Decorator&lt;/a>.&lt;/p>
&lt;p>By splitting up the logic, your methods become smaller, easier to reason about, and easier
to test.&lt;/p></description></item><item><title>Differential serving with webpack encore</title><link>https://backendtea.com/post/webpack-encore-differential-serving/</link><pubDate>Sat, 19 Jun 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/webpack-encore-differential-serving/</guid><description>&lt;p>Recently we&amp;rsquo;ve been looking into differential serving. With our set up there were a few complications,
so in this post i&amp;rsquo;ll share how we overcame those, and how to set up differential serving with webpack encore.&lt;/p>
&lt;p>If you want an explanation of what differential serving is, or how to do with with a normal webpack setup, i
suggest reading &lt;a href="https://johnstew.github.io/differential-serving/" target="_blank" rel="noopener">this post&lt;/a>.&lt;/p>
&lt;p>In our project we use &lt;a href="https://symfony.com/doc/current/frontend.html" target="_blank" rel="noopener">webpack encore&lt;/a>. We use a &lt;code>babel.config.json&lt;/code> to manage our babel settings, as jest can&amp;rsquo;t read that info from the webpack encore file.
We also have a &lt;code>.browserslistrc&lt;/code> file, in which we configure what browsers we support.&lt;/p>
&lt;p>In order to make this work with webpack encore we need to do a few things. First we need to split up our webpack into 3 configs.
We need our &amp;lsquo;base&amp;rsquo; config. I&amp;rsquo;ll call this &lt;code>webpack.base.config.js&lt;/code>. This contains all our webpack configuration, except for &lt;code>setOutputPath&lt;/code> and &lt;code>setPublicPath&lt;/code>. We&amp;rsquo;ll export the Encore object, instead of the &lt;code>getWebpackConfig&lt;/code>. So rename your &lt;code>webpack.config.js&lt;/code> and remove those lines.&lt;/p>
&lt;p>I&amp;rsquo;ve taken the symfony demo project as an example, so the &lt;code>webpack.base.config.js&lt;/code> will look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">Encore&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;@symfony/webpack-encore&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Encore&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">addEntry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;app&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;./assets/app.js&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">addEntry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;login&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;./assets/login.js&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">addEntry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;admin&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;./assets/admin.js&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">addEntry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;search&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;./assets/search.js&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">enableStimulusBridge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;./assets/controllers.json&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">splitEntryChunks&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">enableSingleRuntimeChunk&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">cleanupOutputBeforeBuild&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">enableBuildNotifications&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">enableSourceMaps&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">Encore&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">isProduction&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">enableVersioning&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Encore&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">isProduction&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">enableSassLoader&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">enableIntegrityHashes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Encore&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">isProduction&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">autoProvidejQuery&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">autoProvideVariables&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;window.Bloodhound&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">resolve&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;bloodhound-js&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;jQuery.tagsinput&amp;#34;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;bootstrap-tagsinput&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Encore&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>Now, we need the &amp;rsquo;legacy&amp;rsquo; and the &amp;lsquo;modern&amp;rsquo; builds which will support all the older browsers. For that we create a &lt;code>webpack.legacy.config.js&lt;/code> and a &lt;code>webpack.modern.config.js&lt;/code> with the following content.
The name is set so we can refer to that later in our config file.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// webpack.legacy.config.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">Encore&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;./webpack.base.config&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">BROWSERSLIST_ENV&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;legacy&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Encore&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">setOutputPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;public/legacy-build/&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">setPublicPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/legacy-build&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Encore&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getWebpackConfig&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;legacy&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">config&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// webpack.modern.config.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">Encore&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;./webpack.base.config&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">BROWSERSLIST_ENV&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;modern&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Encore&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">setOutputPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;public/modern-build/&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">.&lt;/span>&lt;span class="nx">setPublicPath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/modern-build&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Encore&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">getWebpackConfig&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">config&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;modern&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">config&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The key here is the &lt;code>BROWSERSLIST_ENV&lt;/code> that is being set. As now we&amp;rsquo;ll edit our &lt;code>.browserslistrc&lt;/code> file. Instead of having the config in a top level in this file, we have 2 versions, the legacy and the modern. By setting the &lt;code>BROWSERSLIST_ENV&lt;/code> environment variable to either, we tell browserlist which one to use.
This is also the reason we need to have these 2 in separate files. Otherwise the env variables will override each other.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># .browserslistrc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">[legacy]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">Last 2 versions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">IE &amp;gt;&lt;/span>&lt;span class="o">=&lt;/span> &lt;span class="s">11&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">not IE 10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">not ie_mob 10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">[modern]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">chrome &amp;gt;&lt;/span>&lt;span class="o">=&lt;/span> &lt;span class="s">80&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">firefox &amp;gt;&lt;/span>&lt;span class="o">=&lt;/span> &lt;span class="s">80&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">safari &amp;gt;&lt;/span>&lt;span class="o">=&lt;/span> &lt;span class="s">12&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now we&amp;rsquo;ll need to edit our package.json file to build both at the same time. So we change our &lt;code>scripts&lt;/code> part to the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;scripts&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dev-server&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;encore dev-server -c webpack.modern.config.js &amp;amp; encore dev-server -c webpack.legacy.config.js&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dev&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;encore dev -c webpack.modern.config.js &amp;amp; encore dev -c webpack.legacy.config.js &amp;amp; wait&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;watch&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;encore dev --watch -c webpack.modern.config.js &amp;amp; encore dev --watch -c webpack.legacy.config.js&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;build&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;encore production --progress -c webpack.modern.config.js &amp;amp; encore production --progress -c webpack.legacy.config.js &amp;amp; wait&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By using &lt;code>&amp;amp;&lt;/code> and &lt;code>wait&lt;/code> we run both processes at the same time, instead of one by one. You can also use &lt;code>npm-run-all&lt;/code> or something similar.&lt;/p>
&lt;p>Next up we&amp;rsquo;ll change our &lt;code>config/packages/webpack_encore.yaml&lt;/code> to tell it about these two builds. Now we can refer to those builds when rendering our tags.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">webpack_encore&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">output_path&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">builds&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">legacy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;%kernel.project_dir%/public/legacy-build&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">modern&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;%kernel.project_dir%/public/modern-build&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>However, it may be cumbersome to remember to call &lt;code>encore_entry_script_tags&lt;/code> twice. Once for legacy, and once for modern. And then also remember to give it the correct attributes. (&lt;code>nomodule&lt;/code> and &lt;code>type=module&lt;/code>). So instead lets create some twig functions to always render both.&lt;/p>
&lt;p>Now, instead of calling &lt;code>encore_entry_script_tags&lt;/code> we call &lt;code>script_tags&lt;/code>, which will render both script tags.
And after we&amp;rsquo;ve replaced the &lt;code>encore_entry_script_tags&lt;/code>, we have differential serving.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">namespace&lt;/span> &lt;span class="nx">App\Twig&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">Symfony\WebpackEncoreBundle\Asset\TagRenderer&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">Twig\Extension\AbstractExtension&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">Twig\TwigFunction&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">EncoreExtension&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">AbstractExtension&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">TagRenderer&lt;/span> &lt;span class="nv">$tagRenderer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getFunctions&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="nx">TwigFunction&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;script_tags&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;legacyEntry&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;is_safe&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;html&amp;#39;&lt;/span>&lt;span class="p">]]),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="nx">TwigFunction&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;link_tags&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;linkEntry&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;is_safe&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;html&amp;#39;&lt;/span>&lt;span class="p">]]),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">legacyEntry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">tagRenderer&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">renderWebpackScriptTags&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;legacy&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;nomodule&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">tagRenderer&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">renderWebpackScriptTags&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;modern&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;type&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;module&amp;#39;&lt;/span>&lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">linkEntry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$name&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">tagRenderer&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">renderWebpackLinkTags&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;legacy&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want to see all the changes, you can check &lt;a href="https://github.com/BackEndTea/demo/commit/b08ebf4fb492fc44c894c0f2d8d8eae87dd2e73a" target="_blank" rel="noopener">this commit&lt;/a>&lt;/p></description></item><item><title>PHP needs an "unknown" type</title><link>https://backendtea.com/post/php-unknown-type/</link><pubDate>Fri, 21 May 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/php-unknown-type/</guid><description>&lt;p>If you are familiar with Typescript, you may know their &lt;code>any&lt;/code> type. With &lt;code>any&lt;/code> you can do anything. It signals that you don&amp;rsquo;t know about the actual type, and that anything goes. The following code is completely valid in Typescript.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">doTheThing&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>: &lt;span class="kt">any&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">input&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">input&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">input&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">keys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">input&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">foo&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">two&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">input&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">input&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In Typescript 3.0 the &lt;code>unknown&lt;/code> type was introduced. &lt;code>unknown&lt;/code> is both like &lt;code>any&lt;/code>, and completely different. &lt;code>unknown&lt;/code> also means that it can be any value, but the logic about it is inverted. With &lt;code>any&lt;/code>, anything is allowed, because we don&amp;rsquo;t know what it is. With &lt;code>unknown&lt;/code>, nothing is allowed, because we don&amp;rsquo;t know what it is.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-typescript" data-lang="typescript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">doTheThing&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>: &lt;span class="kt">unknown&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">input&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nx">input&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// error
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">input&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">toString&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// error
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Object&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">keys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">input&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">map&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="c1">// error
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nx">input&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">foo&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">key&lt;/span>&lt;span class="p">];&lt;/span> &lt;span class="c1">// error
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nx">one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">two&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">input&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// error
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">input&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// error
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>In PHP we have don&amp;rsquo;t have &lt;code>any&lt;/code> or &lt;code>unknown&lt;/code>, instead we have &lt;code>mixed&lt;/code>. Which is an official
type &lt;a href="https://wiki.php.net/rfc/mixed_type_v2" target="_blank" rel="noopener">since PHP 8.0&lt;/a>. And when using static analysis tools the results are mixed.&lt;/p>
&lt;p>If we take the following piece of code, and check it with &lt;a href="https://phpstan.org/r/4d637731-ca53-4c21-8f2e-a1ee6acd94bb" target="_blank" rel="noopener">PHPStan&lt;/a>, we get no errors. With &lt;a href="https://psalm.dev/r/20d0808659" target="_blank" rel="noopener">Psalm&lt;/a> we don&amp;rsquo;t get any errors on level 2, but on level 1 we do.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">doTheThing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">mixed&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$input&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">toString&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$keys&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">array_keys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$input&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$input&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If PHP would have an unknown type, then it could have the same behavior with both tools. Making
changes to &lt;code>mixed&lt;/code> would be a big BC break, especially for legacy projects where there is a lot of
that. Unknown wouldn&amp;rsquo;t be a type that should be in PHP itself, but it could live as an annotation.&lt;/p>
&lt;p>If we have &lt;code>unknown&lt;/code>, we can allow users to use the feature on lower strictness levels. It would also prevent them from having to add potentially thousands of ignores to a baseline file.&lt;/p></description></item><item><title>Using CSS modules in react when testing with enzyme</title><link>https://backendtea.com/post/css-module-enzyme-test/</link><pubDate>Sat, 24 Apr 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/css-module-enzyme-test/</guid><description>&lt;p>We&amp;rsquo;ve been using CSS modules in a react project, and wanted to use those classes
in our tests. However we quickly ran into the problem that none of the classes were found.
Any &lt;code>wrapper.exists&lt;/code> would be false. The problem was with our css modules, as they were not
imported correctly during tests.&lt;/p>
&lt;p>Our code looked like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">import&lt;/span> &lt;span class="nx">styles&lt;/span> &lt;span class="kr">from&lt;/span> &lt;span class="s1">&amp;#39;./QA.scss?module&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">interface&lt;/span> &lt;span class="nx">QAProps&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">question&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">answer?&lt;/span>: &lt;span class="kt">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">isOpen?&lt;/span>: &lt;span class="kt">boolean&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">QA&lt;/span>:&lt;span class="kt">React.FC&lt;/span>&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">QAProps&lt;/span>&lt;span class="p">&amp;gt;&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">({&lt;/span>&lt;span class="nx">question&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">answer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">isOpen&lt;/span>&lt;span class="p">})&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;{&lt;/span>&lt;span class="nx">question&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">p&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">isOpen&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">div&lt;/span> &lt;span class="na">className&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">styles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Answer&lt;/span>&lt;span class="p">}&amp;gt;{&lt;/span>&lt;span class="nx">answer&lt;/span>&lt;span class="p">}&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">div&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>When testing it we tried the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ts" data-lang="ts">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">it&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;has no answer when not opened&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">wrapper&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">shallow&lt;/span>&lt;span class="p">(&amp;lt;&lt;/span>&lt;span class="nt">QA&lt;/span> &lt;span class="na">question&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;hi&amp;#34;&lt;/span> &lt;span class="p">/&amp;gt;)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// passes
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">wrapper&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;.Answer&amp;#39;&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="nx">toBe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">it&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;has an answer when opened&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">()&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kr">const&lt;/span> &lt;span class="nx">wrapper&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">shallow&lt;/span>&lt;span class="p">(&amp;lt;&lt;/span>&lt;span class="nt">QA&lt;/span> &lt;span class="na">question&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;hi&amp;#34;&lt;/span> &lt;span class="na">answer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;answer&amp;#34;&lt;/span> &lt;span class="na">isOpen&lt;/span> &lt;span class="p">/&amp;gt;)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// fails
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nx">expect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">wrapper&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;.Answer&amp;#39;&lt;/span>&lt;span class="p">)).&lt;/span>&lt;span class="nx">toBe&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The problem lied in our fileMock for jest.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// jest.config.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="s2">&amp;#34;moduleNameMapper&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;rootDir&amp;gt;/__mocks__/fileMock.js&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;\\.(scss|less|scss\\?module)$&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;lt;rootDir&amp;gt;/__mocks__/fileMock.js&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>&lt;span class="err">,&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// __mocks__/fileMock.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;content&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Because of this export, &lt;code>styles.Answer&lt;/code> would be undefined. Instead, during our tests we want it
to just return the name of the property. In PHP you would use the &lt;code>__get&lt;/code> magic method.
(Un)fortunately, this is also possible in js, through a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" target="_blank" rel="noopener">&lt;code>Proxy&lt;/code>&lt;/a> object.&lt;/p>
&lt;p>We make a proxy of an empty object, that always returns the property that is being requested. And in the &lt;code>jest.config.js&lt;/code> we update the css files to refer to this new &lt;code>cssMock.js&lt;/code> file.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// __mocks__/cssMock.js
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">const&lt;/span> &lt;span class="nx">target&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">handler&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">get&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="kd">function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">target&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">prop&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">prop&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">proxy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nb">Proxy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">target&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">handler&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">export&lt;/span> &lt;span class="k">default&lt;/span> &lt;span class="nx">proxy&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, our tests pass, and our css modules can be used within our tests.&lt;/p></description></item><item><title>Testing Exceptions in PHPUnit</title><link>https://backendtea.com/post/phpunit-exception-test/</link><pubDate>Fri, 09 Apr 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/phpunit-exception-test/</guid><description>&lt;p>Last week i gave &lt;a href="https://backendtea.com/post/10-phpunit-tips/">10 phpunit tips&lt;/a>.
This week we&amp;rsquo;ll take a look at testing exceptions, which wasn&amp;rsquo;t covered in that post.&lt;/p>
&lt;p>Lets start with some example code that we will be testing. We have the &lt;code>Email&lt;/code> and &lt;code>EmailValidator&lt;/code> classes.
&lt;code>Email&lt;/code> is a value object that makes sure it is a valid email.
We use the &lt;code>EmailValidator&lt;/code> to make sure that the emails are only from our company.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//Email.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Email&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$email&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">){}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$email&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span> &lt;span class="nx">filter_var&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">FILTER_VALIDATE_EMAIL&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;&amp;#34;%s&amp;#34; is not a valid email&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$email&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">asString&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">email&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//EmailValidator.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">EmailValidator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">validateCompanyEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Email&lt;/span> &lt;span class="nv">$email&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span> &lt;span class="nx">str_ends_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">asString&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="s1">&amp;#39;@company.com&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Only &amp;#34;@company.com&amp;#34; emails are allowed&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, we create a test that makes sure we don&amp;rsquo;t allow emails other than &lt;code>@company.com&lt;/code>. We use the &lt;code>expectException&lt;/code> method to tell PHPUnit that we expect this exception. If it is not thrown, or if another exception is thrown, then this test will fail.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">EmailValidatorTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItValidatesEmail&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">expectException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$email&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Email&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;foo@bar.com&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$validator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">EmailValidator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$validator&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">validateCompanyEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>However, we made an error in this test. By starting with the &lt;code>expectException&lt;/code>, the test will pass
as long the &lt;code>InvalidArgumentException&lt;/code> is thrown anywhere within this test. So if we make a typo, and pass &lt;code>foo@barcom&lt;/code> to the &lt;code>Email::create&lt;/code>, an exception will be thrown. And the test will pass.
But then we never test the EmailValidator.&lt;/p>
&lt;p>&lt;strong>To make sure we are properly testing the exception we need to call &lt;code>expectException&lt;/code> just before the method that throws.&lt;/strong>
Now if we run the test, it still passes. But if we make a mistake, and &lt;code>Email::create&lt;/code> throws an exception, our test will fail.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">EmailValidatorTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItValidatesEmail&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$email&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Email&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;foo@bar.com&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$validator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">EmailValidator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">expectException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$validator&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">validateCompanyEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="testing-multiple-exception-paths">Testing multiple exception paths&lt;/h2>
&lt;p>Your method may throw exceptions for different reasons. For example, lets say our email validator
also makes sure that our colleague bob can&amp;rsquo;t log in. So the method would look like this.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">validateCompanyEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Email&lt;/span> &lt;span class="nv">$email&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span> &lt;span class="nx">str_ends_with&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">asString&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="s1">&amp;#39;@company.com&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Only &amp;#34;@company.com&amp;#34; emails are allowed&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">asString&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;bob@company.com&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;bob is no longer allowed to log in&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now we need to test the second path. So lets write that.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testBobCantLogIn&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$email&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Email&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;bob@bar.com&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$validator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">EmailValidator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">expectException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$validator&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">validateCompanyEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But we forgot to turn &lt;code>bar.com&lt;/code> into &lt;code>company.com&lt;/code>.
The test is still green, but we don&amp;rsquo;t actually test that bob can&amp;rsquo;t log in.&lt;/p>
&lt;p>There are a few options here.
We could introduce different exception classes for each error state.
Or we can use &lt;code>expectExceptionMessage&lt;/code> to make sure we get the correct error.
So if we add &lt;code>$this-&amp;gt;expectExceptionMessage('bob is no longer allowed to log in');&lt;/code> just after the &lt;code>expectException&lt;/code> call, then our test turns red. If we then change &lt;code>@bar.com&lt;/code> to &lt;code>@company.com&lt;/code>, our test turns green again.&lt;/p>
&lt;p>The best practice would be to do both. Introducing new exception classes per error means we
can more easily catch the right error. However, you may be dealing with legacy code, where a new
exception class could break things. Validating you get the right error message is really important
if that message faces users. But even if it is only for other devs, you want to make sure you
get the correct exception.&lt;/p>
&lt;h2 id="testing-exceptions-with-data">Testing Exceptions with data&lt;/h2>
&lt;p>Sometimes we use exceptions to pass data back. Perhaps a validator that can have multiple failures, and that has an array of all the errors. Lets take a look at the &lt;code>FormValidator&lt;/code> class, and how we would test it. This class validates some information, and may throw an exception, containing the data of all things that went wrong.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">FormValidator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">validate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DateTimeImmutable&lt;/span> &lt;span class="nv">$start&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">DateTimeImmutable&lt;/span> &lt;span class="nv">$end&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$newId&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$description&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$errors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$start&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="nv">$end&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$errors&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;End must be after start&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$newId&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$errors&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;The new id must be greater than 0&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$description&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$errors&lt;/span>&lt;span class="p">[]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;Description can not be empty&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$errors&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="p">[])&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="nx">FormValidationException&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">fromValidationErrors&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$errors&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we need to test this, using &lt;code>expectException&lt;/code> doesn&amp;rsquo;t make much sense.
&lt;code>expectExceptionMessage&lt;/code> wont help us out either, as it doesn&amp;rsquo;t have a message.
Instead we can use the good only &lt;code>try catch&lt;/code> here.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">FormValidatorTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testValidatesMultipleErrors&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$validator&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">FormValidator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$validator&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">validate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="nx">DateTimeImmutable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;2020-01-01&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="nx">DateTimeImmutable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;1999-01-01&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;FormValidationException was not thrown&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">FormValidationException&lt;/span> &lt;span class="nv">$e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;End must be after start&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;The new id must be greater than 0&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Description can not be empty&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$e&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getErrors&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Important here is the &lt;code>fail&lt;/code> after the validation. If the method throws no exception, we
need to fail the test. If we don&amp;rsquo;t add that call then our test would pass if no exception was thrown.&lt;/p>
&lt;p>But, if you don&amp;rsquo;t need to check any details, other than the exception class, message or code, use
&lt;code>expectException&lt;/code>.&lt;/p>
&lt;h2 id="in-conclusion">In conclusion&lt;/h2>
&lt;p>You can use &lt;code>expectException&lt;/code> to test your exceptions. But only set the exception just before it is thrown. Use &lt;code>expectExceptionMessage&lt;/code> if the message is important, or if it is the only way to
see where something went wrong. Use &lt;code>try catch&lt;/code> if you need to validate specific properties of
the exception.&lt;/p>
&lt;p>For this post, PHPUnit 9.5 and PHP 8.0 were used. If you want to run the tests for yourself, you
can find them &lt;a href="https://github.com/BackEndTea/phpunit-text-exceptions" target="_blank" rel="noopener">on github&lt;/a>.
If you were looking for information on how to test warnings and notices, i have a post about that as well &lt;a href="https://backendtea.com/post/phpunit-test-warnings/">right here&lt;/a>.&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>10 PHPUnit Tips</title><link>https://backendtea.com/post/10-phpunit-tips/</link><pubDate>Fri, 02 Apr 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/10-phpunit-tips/</guid><description>&lt;p>PHPUnit is the defacto testing framework for PHP. In this post i want to share with you my
top 10 tips for PHPUnit. I&amp;rsquo;m using PHPUnit 9.5, but most of these apply to older versions as well. So, lets get this party started.&lt;/p>
&lt;h2 id="1-stop-using-assertequals">1: Stop using &lt;code>assertEquals&lt;/code>&lt;/h2>
&lt;p>One of the most common mistakes is using &lt;code>assertEquals&lt;/code>. Instead, you should be using &lt;code>assertSame&lt;/code>.
When using equals we are doing an &lt;code>==&lt;/code> check, instead of &lt;code>===&lt;/code>. Which means that the following test passes.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testThatPasses&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertEquals&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;3&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>However, if we use &lt;code>assertSame&lt;/code>, we are doing &lt;code>===&lt;/code> checks, which also take types into account.
Meaning the values have to be the same value, and the same type.
So the following test would fail, with the following error message: &lt;code>Failed asserting that 3 is identical to '3'.&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testThatFails&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;3&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="2-use-a-data-provider-when-possible">2: Use a data provider when possible&lt;/h2>
&lt;p>When you need to test a lot of variations of a test, using a data provider will save you from
copy pasting the same set up code everywhere. I wrote a post about this already, so check that out right &lt;a href="https://backendtea.com/post/phpunit-beyond-basic-dataproviders/">here&lt;/a>.&lt;/p>
&lt;h2 id="3-use-createstub-instead-of-createmock">3: Use &lt;code>createStub&lt;/code> instead of &lt;code>createMock&lt;/code>&lt;/h2>
&lt;p>Since PHPUnit 8.4 we have &lt;code>createStub&lt;/code>. Which solved one of the issues with mocking in PHPUnit.
Before that, everything was a mock. While in general testing terms there are mocks, stubs and spies.&lt;/p>
&lt;p>A mock object is an object where you configure expectations on. So you can configure that a method
must be called exactly once, and you can configure with what params the method should be called.&lt;/p>
&lt;p>A stub on the other hand is an object that is just a placeholder. This can mean that it isn&amp;rsquo;t actually used in the part you are testing. Or it means that you only configure that a method will return certain values when called. The difference here is that you don&amp;rsquo;t test how many times a method is called, or with what params.&lt;/p>
&lt;p>By using &lt;code>createStub&lt;/code> you starting using the correct terminology for your test doubles. This should help other better understand what is going on in your test suite.&lt;/p>
&lt;h2 id="4-inject-static-classes">4: Inject static classes&lt;/h2>
&lt;p>Not too long ago we had to test a method that used an external SDK, which was using a lot of global state. A simplified version of our method looked like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">sendRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">bool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Transaction&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">start&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">bool&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">$result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;body&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;data&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;success&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>Transaction::start&lt;/code> could throw a specific exception, and we needed to handle that.
But we can&amp;rsquo;t mock transaction like this, so we needed to change this method to make it testable.
We changed the method to the following, to allow us to inject transaction, for testing only&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">sendRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">?&lt;/span>&lt;span class="nx">Transaction&lt;/span> &lt;span class="nv">$transaction&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">bool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$transaction&lt;/span> &lt;span class="o">??=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Transcation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$transaction&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">start&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">bool&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">$result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;body&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;data&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;success&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, in our test we can inject the Transaction class.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testSendRequest&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$transaction&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">extends&lt;/span> &lt;span class="nx">Transaction&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">start&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">ApiException&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertFalse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sendRequest&lt;/span>&lt;span class="p">([],&lt;/span> &lt;span class="nv">$transaction&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This test now fails, as we get the exception instead of returning false on this exception.
But now we can modify our code to make the test pass.&lt;/p>
&lt;h2 id="5-make-your-tests-fail">5: Make your tests fail&lt;/h2>
&lt;p>You want to make sure your tests are doing their job. So you want to be sure that they are
actually testing what you think they are. So lets take a look at the following basic validation
function. It checks that the two inputs are both integers.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">validateInput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">is_int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$one&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;one must be an int&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nx">is_int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$two&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;two must be an int&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We could then create a test for this method like so:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testBothMustBeIntegers&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">expectException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">validateInput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;2&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But, if we remove the first (or the second) check, the test still passes. This test isn&amp;rsquo;t strict enough. Instead, we need more test cases.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">function validateInput($one, $two): void
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- if (!is_int($one)) {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- throw new InvalidArgumentException(&amp;#39;one must be an int&amp;#39;);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- }
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if (!is_int($two)) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> throw new InvalidArgumentException(&amp;#39;two must be an int&amp;#39;);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @dataprovider provideNonIntegersCases
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testBothMustBeIntegers&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">expectException&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">InvalidArgumentException&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">validateInput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$one&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$two&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideNonIntegersCases&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Generator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;2&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;2&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">3.5&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, we use a data provider to test different cases.
And if we remove one of the checks now, a test will fail.&lt;/p>
&lt;p>We can do this manually, or we can use a mutation testing framework, like &lt;a href="https://infection.github.io/" target="_blank" rel="noopener">infection&lt;/a> to help us with ths.&lt;/p>
&lt;h2 id="6-use-static-analysis-on-your-tests">6: Use static analysis on your tests.&lt;/h2>
&lt;p>You should be using static analysis on your source code. But you should also use it on your tests.
It will help you catch bugs in your tests, just like it does in normal code.&lt;/p>
&lt;p>But, it will also make sure you aren&amp;rsquo;t testing &lt;strong>too much&lt;/strong>. For example if you have the following function that sends an api request.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string|resource $data
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">sendApiRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$data&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">client&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">post&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;body&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$data&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This method takes either a string, or a resource, and uses that as the data to send. If, in your tests you pass a class to this method, just to see what happens, that would give you an error in your static analysis tool. So, since you won&amp;rsquo;t be able to do that in your normal code, you don&amp;rsquo;t have to worry about it in your tests.&lt;/p>
&lt;p>An important thing to note here, is that you should be running your test suite on the same level
as your &amp;rsquo;normal&amp;rsquo; code. If your test suite is stricter, then the error you get there may not show up in your normal code. Ideally both the source and test folder should be checked in the same process.&lt;/p>
&lt;h2 id="7-test-the-simple-stuff">7: Test the simple stuff&lt;/h2>
&lt;p>To people who don&amp;rsquo;t like tests, they always fall in one of two categories. The code is either impossible to test, or it is so simple that it is not worth testing. I&amp;rsquo;d say, write the simple tests. &lt;strong>If the code is worth writing, its worth testing.&lt;/strong> Generally the time you spend on writing the simple tests isn&amp;rsquo;t what is going to slow you down.&lt;/p>
&lt;p>Even a test like this is worth writing. You wouldn&amp;rsquo;t be the first (or the last) to accidentally
assign the wrong property in the constructor, or read the wrong property in a getter.&lt;/p>
&lt;p>Especially if it is a large pull request, these are the kind of thing that get looked over.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItCanBeCreated&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$link&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Link&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;/url&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;name&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$link&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getName&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/url&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$link&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getUrl&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="8-do-a-sanity-check">8: Do a sanity check&lt;/h2>
&lt;p>Be kind to yourself, and do a sanity check before test. Especially something that
interacts with a global state of any kind. If you do a quick check before hand, you
make sure your test is still doing what it is supposed to do.&lt;/p>
&lt;p>For example, we wanted to make sure our &lt;code>SessionFactory&lt;/code> did not start a php session
when creating the session object. Here we check that we do not have a session id,
before we ever start the test. I always add a &lt;code>sanity check&lt;/code> comment, so that everyone
understands this is not what is actually tested, but a prerequisite.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @runInSeparateProcess
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testNewSessionDoesNotStartAPhpSession&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// sanity check
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">session_id&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;A session was already started before this test ran.&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$factory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">SessionFactory&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$session&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$factory&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">newSession&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">session_id&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertFalse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$session&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">isStarted&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="9-test-3-or-more-variants-of-your-loops">9: Test 3 (or more) variants of your loops&lt;/h2>
&lt;p>Whenever you have a loop in your code, whether it&amp;rsquo;s a &lt;code>for&lt;/code>, &lt;code>foreach&lt;/code>, &lt;code>while &lt;/code> or &lt;code>do while&lt;/code> loop. Try to test at least 3 versions of it.&lt;/p>
&lt;ul>
&lt;li>No loops&lt;/li>
&lt;li>1 loop&lt;/li>
&lt;li>3 loops&lt;/li>
&lt;/ul>
&lt;p>This depends on your code though. If the loop is never reached if the input array is empty, then
that it not something you have to consider. But by testing these 3 versions you account for
a few potential bugs.&lt;/p>
&lt;p>You may be using variables form inside the loop after it. Since these variables don&amp;rsquo;t exists if you never loop, this should error in your tests.
Or when a variable is reused in the next loop by accident, for example if it is declared within an &lt;code>if&lt;/code>. When you do multiple loops, this should give you a wrong result if you do multiple loops.&lt;/p>
&lt;h2 id="10-just-start-writing-the-tests">10: Just start writing the tests&lt;/h2>
&lt;p>A mediocre test is better then no tests. You may write test that aren&amp;rsquo;t perfect.
But when you start writing code it&amp;rsquo;s generally not perfect either. The only real way to improve
the tests you write is by writing more of them.&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Help my Doctrine migrations are broken!</title><link>https://backendtea.com/post/help-my-doctrine-migrations-are-broken/</link><pubDate>Thu, 25 Mar 2021 21:16:07 +0100</pubDate><guid>https://backendtea.com/post/help-my-doctrine-migrations-are-broken/</guid><description>&lt;p>Recently we ran into a problem with doctrine migrations. The schema was manually edited, and
wrong migrations were committed to the repository. For a dev (and even staging) env you could just drop the database, fix the migrations, and run them again. But we didn&amp;rsquo;t want to lose our production data.
So how do we fix this?&lt;/p>
&lt;p>We really had 2 problems. Our migrations were wrong, and the (production) database was not in the correct state.&lt;/p>
&lt;h2 id="fixing-the-migrations">Fixing the migrations&lt;/h2>
&lt;p>The migrations looked (something like) this:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Migration 1:&lt;/strong> Create users table&lt;/li>
&lt;li>&lt;strong>Migration 2:&lt;/strong> Create products table. Create category table&lt;/li>
&lt;li>&lt;strong>Migration 3:&lt;/strong> Create category table. Alter users table&lt;/li>
&lt;/ul>
&lt;p>So migration 2 and 3 had some of the same content. The fix here was to just remove the creation of the category
table from migration 3. Normally you shouldn&amp;rsquo;t edit migrations, but this was the only way to keep them working.
We tested the migrations in a local environment, and were able to run them starting from an empty DB. The &lt;code>down&lt;/code> migrations were also working.&lt;/p>
&lt;p>This was step one, as now anyone can work on the project and get their DB in the correct state, without manual extra work. However there was another issue, the production database.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="fixing-the-production-database">Fixing the production database&lt;/h2>
&lt;p>So now we were ready to take a look at the production database. It turned out the database was in the &amp;lsquo;correct&amp;rsquo; state. However the migrations themselves were only ran up until migration 2. Migration 3 was never ran, but the altering of the users table had been done. Thankfully, doctrine has a command for this.
The &lt;code>version&lt;/code> command will add (or delete) a migration from your migration table, without executing it.
In symfony this is exposed as &lt;code>bin/console doctrine:migrations:version&lt;/code>.&lt;/p>
&lt;p>So to manually add the migration, we ran the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ bin/console doctrine:migrations:version DoctrineMigrations&lt;span class="se">\\&lt;/span>Version20210125124213 --add
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>DoctrineMigrations\\Version20210125124213&lt;/code> Is the FQN of the migration. The &lt;code>--add&lt;/code> is to add the migration. If we needed to remove one we could&amp;rsquo;ve used &lt;code>--delete&lt;/code>.
Using this command is safer than manually editing the migrations table, because if you make a typo in the name, doctrine wont add it.&lt;/p>
&lt;h2 id="lessons-learned">Lessons learned&lt;/h2>
&lt;p>There are a few lessons to learn here. The first one is that if you mess up your migrations and your database,
there is a way to fix it. However, its even better to prevent these issues from happening. There are a few
precautions you can take.&lt;/p>
&lt;p>&lt;strong>Test your migrations&lt;/strong>&lt;/p>
&lt;p>Make it a step in your CI to run your migrations, from an empty DB to the latest version, and from the last version all the way back.
This will stop you from committing faulty migrations, and can be as simple as the following steps:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">script&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">bin/console doctrine:migrations:migrate --no-interaction&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># migrations up&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">bin/console doctrine:schema:validate&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># check that the entities and the schema match&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">bin/console doctrine:migrations:migrate first --no-interaction&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># migrations down&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Don&amp;rsquo;t manually change the DB&lt;/strong>&lt;/p>
&lt;p>We wouldn&amp;rsquo;t have gotten into this trouble if we had only used the migrations to update the DB.
A developer had ran &lt;code>php bin/console doctrine:schema:update --force&lt;/code> to get the DB into the latest state. This (and manual ALTER ) queries shouldn&amp;rsquo;t be used if you want your DB to be managed by migrations.&lt;/p>
&lt;h2 id="closing-words">Closing words&lt;/h2>
&lt;p>In reality this was a small and new project. If we had wiped the DB and recreated it, the data would&amp;rsquo;ve been added back within 5 minutes. But now we have the experience and knowledge on how to deal with these issues. When life gives you a safe training opportunity, take it!&lt;/p>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>A problem with Twig</title><link>https://backendtea.com/post/a-problem-with-twig/</link><pubDate>Thu, 18 Mar 2021 18:31:13 +0100</pubDate><guid>https://backendtea.com/post/a-problem-with-twig/</guid><description>&lt;p>I&amp;rsquo;m a fan of twig, and wouldn&amp;rsquo;t consider moving back to plain php. But, it does come with a few problems.
In this post we&amp;rsquo;ll explore one of the problems i have with twig, and how to work around it.&lt;/p>
&lt;h2 id="the-problem">The problem&lt;/h2>
&lt;p>When you use a twig file, you do not know what variables it needs, what variables i &lt;em>can&lt;/em> use, and what
types those variables should be. You have to read the template, or execute it and see what errors you get.&lt;/p>
&lt;p>This isn&amp;rsquo;t bad if you&amp;rsquo;ve worked on a project for a long time. But when you work on a project that isn&amp;rsquo;t yours,
it can be difficult to understand what a template needs. Is that variable passed to the template, or
is it a global? What type is it supposed to be?
We&amp;rsquo;ve had issues multiple times where a variable wasn&amp;rsquo;t passed to a template, and then the site went down.&lt;/p>
&lt;p>You can check this by having e2e tests that check all possible paths, and verify your twig templates.
But most code bases won&amp;rsquo;t have those, or the tests take too long to be of value. What if we could be sure
what our templates need, and make sure it gets exactly that?&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="a-solution">A solution&lt;/h2>
&lt;p>At first i came up with the following solution. A service per template that would require the correct
variables to be passed. This way, all you need to do is find the correct service for the template, and you
know what variables it needs.
The problem here is that you will need a service for every single template. This may be okay for small apps,
but for bigger apps this seems like a bad solution.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">AccountPageAction&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">AccountPageView&lt;/span> &lt;span class="nv">$view&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">Security&lt;/span> &lt;span class="nv">$security&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__invoke&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Response&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Deal with things
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Response&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">view&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">render&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">security&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getUser&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">AccountPageView&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">Environment&lt;/span> &lt;span class="nv">$twig&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">render&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">User&lt;/span> &lt;span class="nv">$user&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">twig&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">render&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;account/view.html.twig&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;user&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$user&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I posted about my ideas on &lt;a href="https://twitter.com/BackEndTea/status/1368501985821876224" target="_blank" rel="noopener">twitter&lt;/a> and
got some responses.
The &lt;a href="https://twitter.com/azjezz/status/1368534292951883777" target="_blank" rel="noopener">response&lt;/a> from &lt;a href="https://twitter.com/azjezz" target="_blank" rel="noopener">@azjezz&lt;/a>
seemed like the perfect solution.&lt;/p>
&lt;p>One thing that i liked about this approach is also the &lt;code>Renderer&lt;/code> class that was used. He gave the example of a
&lt;code>Responder&lt;/code> class that he used later in that thread.&lt;/p>
&lt;p>So after a few changes i ended up with the following Responder and template:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Responder&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * Render the given twig template and return an HTML response.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param array&amp;lt;string, string|list&amp;lt;string&amp;gt;&amp;gt; $headers
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @throws TwigError
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">render&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Template&lt;/span> &lt;span class="nv">$template&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="nv">$headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[])&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Response&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">twig&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">render&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$template&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getTemplate&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nv">$template&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getContext&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Response&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$status&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$headers&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nv">$response&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">headers&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">has&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$response&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">headers&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;text/html; charset=UTF-8&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$response&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// The rest of the class
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">interface&lt;/span> &lt;span class="nx">Template&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getTemplate&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sd">/** @return array&amp;lt;mixed&amp;gt; */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getContext&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, even when the template is used in multiple end points, its clear what the template needs.
Passing in the wrong type will alert a tool like &lt;code>PHPStan&lt;/code>. The same goes for forgetting to pass in a
certain variable. We can even use a tool like &lt;a href="https://github.com/qossmic/deptrac" target="_blank" rel="noopener">deptrac&lt;/a> to make sure
all our controllers use the responder, instead of rendering twig templates directly.&lt;/p>
&lt;p>If your template has a lot of optional variables, you can consider using &lt;a href="https://wiki.php.net/rfc/named_params" target="_blank" rel="noopener">named arguments&lt;/a>. This will save you from having to pass in a lot of &lt;code>null&lt;/code> or other
default values.&lt;/p>
&lt;p>Using this, the original AccountPageAction now looks like this.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">AccountPageAction&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">Responder&lt;/span> &lt;span class="nv">$responder&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__invoke&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Response&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Deal with things
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">responder&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">render&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">new&lt;/span> &lt;span class="nx">AccountPageTemplate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">security&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getUser&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">AccountPageTemplate&lt;/span> &lt;span class="k">implements&lt;/span> &lt;span class="nx">Template&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">private&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="nv">$user&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getTemplate&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;account/view.html.twig&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sd">/** @return array&amp;lt;mixed&amp;gt; */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getContext&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;user&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">user&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By using this &lt;code>Template&lt;/code> interface and the &lt;code>Responder&lt;/code> we now always know what variables our templates need.
We will know if we forgot a variable. It can save you a broken website due to a missing variable, and should make your twig a bit safer.&lt;/p></description></item><item><title>Explore Your Types</title><link>https://backendtea.com/post/explore-your-types/</link><pubDate>Thu, 11 Mar 2021 21:18:16 +0100</pubDate><guid>https://backendtea.com/post/explore-your-types/</guid><description>&lt;p>Don&amp;rsquo;t worry, this isn&amp;rsquo;t a &amp;lsquo;what type of sandwich are you?&amp;rsquo; kinda post.
Instead we&amp;rsquo;ll look at how we can safely add types to our legacy code.&lt;/p>
&lt;h2 id="adding-types">Adding types&lt;/h2>
&lt;p>There are really two ways of adding types. You either declare what you &lt;strong>want&lt;/strong> your types to be, or you
declare what the types &lt;strong>can&lt;/strong> be. When writing new code we should always be precise in our types, so we declare
what we want. We can say that a property is a string, or that a method only takes a &lt;code>User&lt;/code> class as a parameter.&lt;/p>
&lt;p>But for our legacy code, this is a bit different. Lets take, for example this snippet from a &lt;code>Login&lt;/code> class i
recently had to deal with. While password is annotated as a string, when we look at the implementation, we see
that false is also an option.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Login&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @var string
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nv">$password&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">login&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$password&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// stuff
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">password&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$password&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>Tools like Psalm and PHPStan will tell us there is an error here, or you could find it on your own. This is a
problem we&amp;rsquo;d like to solve, but there are multiple ways of doing so.
One would be to add a &lt;code>(string)&lt;/code> cast, before the assignment of &lt;code>$password&lt;/code>. The problem with that, is that we
are changing behaviour of the code. Another place may have a check like &lt;code>$this-&amp;gt;password === false&lt;/code>.&lt;/p>
&lt;p>The best solution would be to annotate password with &lt;code>string|false&lt;/code>. And if later it turns out it can also be a
resource, pointint to a private key, then we turn it into &lt;code>string|false|resource&lt;/code>. Now, if it can also be a &lt;code>Password&lt;/code> object, then we make it &lt;code>string|false|resource|Password&lt;/code>. We must however, resist just ploping down
&lt;code>mixed&lt;/code> and calling it a day.&lt;/p>
&lt;p>By using &lt;code>mixed&lt;/code> we might as well have not added any types. Instead, we want to document our types as precise as
possible, wherever we can. Now, the example of the Login class is small,
but when you have a class with 200 methods, all calling each other and using its output, finding out what type something is can be difficult. You have to start with a type you are sure of. And if you can, make it an explicit type, in the language.&lt;/p>
&lt;p>For example, what if we have the following method in a legacy class. When we read the code, we still don&amp;rsquo;t
know what &lt;code>$orderData&lt;/code> can be. Maybe the &lt;code>fetchOrder&lt;/code> method will hint us to that. But when we read it we can
guarantee that it returns an &lt;code>int&lt;/code> or &lt;code>false&lt;/code>. We can add it right away, and know we won&amp;rsquo;t change any behaviour.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Legacy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 100 methods
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getOrderId&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$orderData&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$orderId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">database&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fetchOrder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$orderData&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span> &lt;span class="nv">$orderId&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">$orderId&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 100 more methods
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So now we&amp;rsquo;ve added the type to our method. We may need to update the baseline of our static analysis tool,
or maybe we don&amp;rsquo;t have to do any extra work. Now we can be sure that when we use the &lt;code>getOrderId&lt;/code> method, we
get either an integer, or false. This (hopefully) means that we can add types elsewhere.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Legacy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 100 methods
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getOrderId&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$orderData&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">int&lt;/span>&lt;span class="o">|&lt;/span>&lt;span class="k">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$orderId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">database&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">fetchOrder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$orderData&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span> &lt;span class="nv">$orderId&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">$orderId&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 100 more methods
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="so-whats-the-point">So whats the point&lt;/h2>
&lt;p>The first point of adding these types is to increase our understanding of what this code does. By giving it
the proper types, we may detect dead code, or find bugs. If we keep up this, and &lt;a href="https://backendtea.com/post/boyscout-rule/">the boyscout rule&lt;/a> long enough, then it may not be legacy code anymore.&lt;/p>
&lt;p>The second point is that your &amp;rsquo;new&amp;rsquo; or better code probably still has to connect with legacy code. So by
adding a bit more certainty, we are a bit safer that the code does what we expect it to do.&lt;/p>
&lt;p>So go out there, and explore all the types your codebase has. So what if the parameter can be one of 12 interfaces, a string, or a boolean. Only once the horrors are documented do you have a chance to
improve them.&lt;/p></description></item><item><title>You should be using PHPStans bleeding edge</title><link>https://backendtea.com/post/use-phpstan-bleeding-edge/</link><pubDate>Thu, 04 Mar 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/use-phpstan-bleeding-edge/</guid><description>&lt;p>Does your project use PHPStan? Then you really should be using the bleeding edge config,
regardless of what level you are running. It will make transition to the newer version much easier.&lt;/p>
&lt;h2 id="what-is-bleeding-edge">What is bleeding edge&lt;/h2>
&lt;p>To preserve backwards compatibility as much as possible, new rules, and new settings are
delayed until the next major version. For example, using null coalesce on a
variable that can not be null will be an error in the next version.
But in this version it does not. Bleeding edge gives you the features of the next PHPStan version, right now.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="how-to-activate-it">How to activate it&lt;/h2>
&lt;p>To start using bleeding edge, you need to include it in your &lt;code>phpstan.neon&lt;/code>. The next time you run PHPStan, you will get all the new errors.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">includes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">vendor/phpstan/phpstan/conf/bleedingEdge.neon&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="why-activate-it">Why activate it&lt;/h2>
&lt;p>Updating composer dependencies should be part of the normal workflow of a team. Whether you
take time every monday to do updates, or you update constantly.
When using bleeding edge, you have a little bit of work whenever something new gets added.
You need to fix the errors, or add them to an ignore.
This is only one new rule or setting at a time, meaning you generally don&amp;rsquo;t have to spend all that much time.&lt;/p>
&lt;p>When you wait until the next major version, you suddenly have maybe 10 new rules and settings you need to account for. Meaning you&amp;rsquo;ll have to spend a far bigger chunk of time then. This could also make it easier to just say &amp;ldquo;screw it&amp;rdquo;, and ignore all the new errors.
By doing the updates as they come, you keep the tasks small.&lt;/p>
&lt;p>You don&amp;rsquo;t need to be at level max in order to use bleeding edge. If a new rule gets added at level 7, and you are on level 5, that new rule wont activate.&lt;/p>
&lt;h2 id="when-not-to-activate-it">When not to activate it&lt;/h2>
&lt;p>I would only suggest to do this for a project, with a &lt;code>composer.lock&lt;/code> file. If you are creating a library, this could mean that suddenly a nightly build is failing, or that a PR
of gets blocked as the new PHPStan errors need to be fixed first.&lt;/p>
&lt;p>Even in a library, you could still run a check with bleeding edge, but allow it to fail. That way you still get the feedback that in the next version things will break, but it wont block contributions.&lt;/p>
&lt;h2 id="in-summary">In summary&lt;/h2>
&lt;p>If your project uses PHPStan, use bleeding edge. It will make your upgrade path smaller,
and allows you to use the features of the next version today.&lt;/p></description></item><item><title>PHPUnit beyond basics: Dataproviders</title><link>https://backendtea.com/post/phpunit-beyond-basic-dataproviders/</link><pubDate>Fri, 26 Feb 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/phpunit-beyond-basic-dataproviders/</guid><description>&lt;p>Once you have set up your first unit tests, and you have a good &lt;a href="https://backendtea.com/post/phpunit-beyond-basic-config/">configuration&lt;/a>, its time to add a lot of tests.
Lets take a look at using data providers, as a way to test with a lot of data.&lt;/p>
&lt;p>For this example we&amp;rsquo;ll test a piece of code that is supposed to do the following. Given the array &lt;code>['a', 'b', 'c']&lt;/code>, return the string &lt;code>a a-b a-b-c b b-c c&lt;/code>. This function has to
combine the array values to that string.&lt;/p>
&lt;p>Now we could write a test that tests this case. However, we need to make sure this works in
more than just this case. It needs to handle duplicate entries, an empty array, an array with just one element, and so on. We could add a test method for every case, but there is
an easier solution: dataproviders.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItCombines&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;a a-b a-b-c b b-c c&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">KeyCombiner&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">combine&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;b&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;c&amp;#39;&lt;/span>&lt;span class="p">]));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A dataprovider, will provide the input for your tests. Normally a test method does not take
any arguments, but with a dataprovider, you can execute it many times, with different data
every time. So lets change our test, to use a data provider, for just this case.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @dataProvider provideCombineCases
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItCombines&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$expectedOutput&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$expectedOutput&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">KeyCombiner&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">combine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$input&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideCombineCases&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Generator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[[&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;b&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;c&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;a a-b a-b-c b b-c c&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We configure our test method to have a dataprovider by the annotation:
&lt;code>@dataProvider provideCombineCases&lt;/code>.
(If you are using PHPUnit 10, you can use the &lt;code>Datprovider&lt;/code> attribute rather than the annotation.).
This has to refer to a public method on the same class, which will return an &lt;code>iterable&lt;/code>.
This iterable has to be an iterable of arrays, where every array matches the arguments of the method it provides data to.&lt;/p>
&lt;p>In our case, we have a Generator which provides an array, containing an array, and a string.
These match our test method, which takes an array, and a string. Now, adding more test cases, is as simple as adding a new case to the data provider. So lets add the test cases we talked about. And, if we discover a bug, we can add a new test case to the provider, and make sure we cover that too.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideCombineCases&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Generator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[[&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;b&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;c&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;a a-b a-b-c b b-c c&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[[],&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[[&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;b&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;a a-a a-a-b a a-b b&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[[&lt;/span>&lt;span class="s1">&amp;#39;b&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;b&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But, we&amp;rsquo;re not done here. If we add keys to our dataprovider, we get that information when
our tests fail, or when we use the &lt;code>--testdox&lt;/code> flag. Currently it will output something along the lines of &amp;ldquo;Test it combines with dataset 0&amp;rdquo;.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideCombineCases&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Generator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="s1">&amp;#39;multiple keys&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">[[&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;b&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;c&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;a a-b a-b-c b b-c c&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="s1">&amp;#39;an empty list&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">[[],&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="s1">&amp;#39;duplicate keys&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">[[&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;b&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;a a-a a-a-b a a-b b&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="s1">&amp;#39;a single key&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">[[&lt;/span>&lt;span class="s1">&amp;#39;b&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s1">&amp;#39;b&amp;#39;&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, if we use the testdox flag, we get an output like &amp;ldquo;Test it combines with multiple keys&amp;rdquo;.
And if we get a failure, the error message will also contain the key you used.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="additional-things">Additional things&lt;/h2>
&lt;h3 id="multiple-providers">Multiple providers&lt;/h3>
&lt;p>You can use multiple dataproviders on a single method. This can be useful for validation methods. Where you can split up the cases that pass validation, and cases that dont.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @dataProvider provideAlnumCases
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @dataProvider provideNonAlnumCases
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItValidatesAlnum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bool&lt;/span> &lt;span class="nv">$isAlnum&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$isAlnum&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Validator&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">isAlnum&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$input&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideAlnumCases&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Generator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;a&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">true&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// more
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideNonAlnumCases&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Generator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">yield&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;_&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// more
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="code-coverage">Code coverage&lt;/h3>
&lt;p>Any code that is executed during the dataprovider does not count toward code coverage. So
if your object under test is only constructed in dataproviders the &lt;code>__construct&lt;/code> method won&amp;rsquo;t be considered covered.&lt;/p>
&lt;h3 id="errors">Errors&lt;/h3>
&lt;p>When an Exception (or Error) is thrown in your dataprovider, none of the test cases are executed. Instead it will count as a single (failed) test.&lt;/p>
&lt;h3 id="when">When&lt;/h3>
&lt;p>Data providers are ran before the &lt;code>setUp&lt;/code> or &lt;code>setUpBeforeClass&lt;/code> method is called. Meaning you can&amp;rsquo;t use any properties set in those methods.&lt;/p></description></item><item><title>PHPUnit beyond basics: configuration</title><link>https://backendtea.com/post/phpunit-beyond-basic-config/</link><pubDate>Fri, 19 Feb 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/phpunit-beyond-basic-config/</guid><description>&lt;p>If you just got started with PHPUnit, its configuration file may be a bit daunting.
Today we&amp;rsquo;re gonna walk through (what i consider) the ideal config file. If you&amp;rsquo;re just here
to copy paste the config, then you can find it at the&lt;a href="#tldr">bottom&lt;/a> &amp;#x1f447;.&lt;/p>
&lt;p>A minimum &lt;code>phpunit.xml&lt;/code> may look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;phpunit&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;testsuites&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;testsuite&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;Tests&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;directory&amp;gt;&lt;/span>tests/&lt;span class="nt">&amp;lt;/directory&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/testsuite&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/testsuites&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/phpunit&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The first thing we can do to improve this, is link the &lt;code>xsd&lt;/code>. By doing so we now get
auto completion, and validation of our xml file. If you start typing anywhere, your IDE will
help you out.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;phpunit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xsi:noNamespaceSchemaLocation=&lt;/span>&lt;span class="s">&amp;#34;vendor/phpunit/phpunit/phpunit.xsd&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!--More--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/phpunit&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img src="https://backendtea.com/img/phpunit-xsd-autocomplete.png" alt="PHPUnit autocomplete" loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>PHPUnit comes with a lot of good defaults, but there are a few settings i like to set.
Colors is just because i like my colors, but the other settings are important.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;phpunit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">colors=&lt;/span> &lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">executionOrder=&lt;/span>&lt;span class="s">&amp;#34;random&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">failOnWarning=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">failOnRisky=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">failOnEmptyTestSuite=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">beStrictAboutOutputDuringTests=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">verbose=&lt;/span> &lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&amp;lt;!--More--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By using a random execution order, we make sure our tests aren&amp;rsquo;t depending on each other.
For example one test writing to a DB, and the other one is reading from it. This is bad,
because then if the first test fails, the next one will too. And it may be unclear why.
It also means we can&amp;rsquo;t &lt;em>just&lt;/em> run the second test, as we need to run the other one first.&lt;/p>
&lt;p>Fail on waring, and fail on risky will just make your test suite a bit more strict.
Note that this setting is not about &amp;rsquo;normal&amp;rsquo; warnings, but instead warnings from phpunit.
There are several reasons why phpunit may consider a test risky, which you can find &lt;a href="https://phpunit.readthedocs.io/en/9.5/risky-tests.html?highlight=risky#risky-tests" target="_blank" rel="noopener">here&lt;/a>.
With fail on risky we make sure those tests fail as well.&lt;/p>
&lt;p>Fail on empty test suite will make sure your test suite actually contains a test.
When you forget an annotation, or have a typo in your test name, then suddenly its not a
test anymore, and you code may go untested, without you noticing.&lt;/p>
&lt;p>Be strict about output also helps your tests be more strict. Generally, when your tests have
output, that is something unintentional. Even if your code under test does an echo, you can
catch that with &lt;code>ob_get_clean()&lt;/code>, to validate the output of that. So any output that leaks out, should be an error.&lt;/p>
&lt;p>Verbose is debatable. It does give you the most information possible on failures and errors,
but if you have a test suite with a lot of skipped tests, it may be too verbose.
Your best bet is to turn it on, and only if you find the output too much, turn it off.&lt;/p>
&lt;p>Now that we have configured the phpunit element, there are only a few things left to do.
The next thing we want to do is add some ini configurations.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;phpunit&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;php&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ini&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;error_reporting&amp;#34;&lt;/span> &lt;span class="na">value=&lt;/span>&lt;span class="s">&amp;#34;-1&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ini&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;display_errors&amp;#34;&lt;/span> &lt;span class="na">value=&lt;/span>&lt;span class="s">&amp;#34;On&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/php&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!--More--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With &lt;code>error_reporting&lt;/code> set to &lt;code>-1&lt;/code>, all notices, deprecations, warnings etc, will be reported
And with the default configuration, these are converted into exceptions, which mark
your test failed.&lt;/p>
&lt;p>Setting &lt;code>display_errors&lt;/code> to &lt;code>On&lt;/code> will probably not be needed for most projects. But if &lt;code>display_errors&lt;/code> is off, and &lt;code>log_errors&lt;/code> is too, then you will not see any error output.
These are on by default in PHP, but if you, or someone who works on your project, has a wonky environment, then you won&amp;rsquo;t see any output. By forcing it to &lt;code>On&lt;/code>, you will still see output of fatal errors.&lt;/p>
&lt;p>Last but not least, we&amp;rsquo;ll set up our coverage:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;phpunit&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;coverage&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;include&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;directory&lt;/span> &lt;span class="na">suffix=&lt;/span>&lt;span class="s">&amp;#34;.php&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>src/&lt;span class="nt">&amp;lt;/directory&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/include&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/coverage&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&amp;lt;&lt;/span>/phpunit
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We tell phpunit that &lt;strong>when&lt;/strong> it collects coverage, this is where to find it.
Note that we don&amp;rsquo;t set a &lt;code>&amp;lt;logging&amp;gt;&lt;/code> element, as that would cause phpunit to always
collect coverage if possible. Instead, only pass it when it is needed. We don&amp;rsquo;t want to always generate coverage, as that will make your tests a lot slower.&lt;/p>
&lt;p>Now, when you want to create a code coverage html report, you can do the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">vendor/bin/phpunit --coverage-html&lt;span class="o">=&lt;/span>coverage
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> coverage
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">php -S localhost:1313
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you got a nice looking code coverage report on &lt;code>http://localhost:1313&lt;/code>.&lt;/p>
&lt;h2 id="tldr">TLDR&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;phpunit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xsi:noNamespaceSchemaLocation=&lt;/span>&lt;span class="s">&amp;#34;vendor/phpunit/phpunit/phpunit.xsd&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">colors=&lt;/span> &lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">executionOrder=&lt;/span>&lt;span class="s">&amp;#34;random&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">failOnWarning=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">failOnRisky=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">failOnEmptyTestSuite=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">beStrictAboutOutputDuringTests=&lt;/span>&lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">verbose=&lt;/span> &lt;span class="s">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;php&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ini&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;display_errors&amp;#34;&lt;/span> &lt;span class="na">value=&lt;/span>&lt;span class="s">&amp;#34;On&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;ini&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;error_reporting&amp;#34;&lt;/span> &lt;span class="na">value=&lt;/span>&lt;span class="s">&amp;#34;-1&amp;#34;&lt;/span>&lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/php&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;testsuites&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;testsuite&lt;/span> &lt;span class="na">name=&lt;/span>&lt;span class="s">&amp;#34;Tests&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;directory&amp;gt;&lt;/span>tests/&lt;span class="nt">&amp;lt;/directory&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/testsuite&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/testsuites&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;coverage&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;include&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;directory&lt;/span> &lt;span class="na">suffix=&lt;/span>&lt;span class="s">&amp;#34;.php&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>src/&lt;span class="nt">&amp;lt;/directory&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/include&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/coverage&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/phpunit&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Every day design pattern: Builder</title><link>https://backendtea.com/post/every-day-design-pattern-builder/</link><pubDate>Fri, 12 Feb 2021 19:58:22 +0100</pubDate><guid>https://backendtea.com/post/every-day-design-pattern-builder/</guid><description>&lt;p>Just like the &lt;a href="https://backendtea.com/post/every-day-design-pattern-factory/">factory&lt;/a> pattern, the builder is a creational pattern,
meaning it is about how objects are created. But unlike a factory, a builder allows you to
build an object in parts. I tend to use it for creating objects that take a configuration.&lt;/p>
&lt;p>Lets start with an example. This class builds a guzzle client, with a certain config.
Normally we have a timeout of 10 seconds, with a 5 second timeout for both connect and read.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">GuzzleHttp\Client&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">use&lt;/span> &lt;span class="nx">GuzzleHttp\RequestOptions&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">GuzzleClientBuilder&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="nv">$config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">RequestOptions&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">TIMEOUT&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;defaults&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">RequestOptions&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">CONNECT_TIMEOUT&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">RequestOptions&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">READ_TIMEOUT&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">defaultClient&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Client&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">init&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">build&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">init&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">withTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$timeout&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">config&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">RequestOptions&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">TIMEOUT&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$timeout&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">withConnectTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$timeout&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">config&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;defaults&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="nx">RequestOptions&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">CONNECT_TIMEOUT&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$timeout&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">withReadTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$timeout&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">self&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">config&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;defaults&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="nx">RequestOptions&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">READ_TIMEOUT&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$timeout&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">build&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Client&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Client&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">config&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>So if we want to use this builder to create a client with a 30 second timeout, we would do the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$guzzle&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">GuzzleClientBuilder&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">init&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">withTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">30&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">build&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>By using the builder we still have all the other config, so the timeout for connection would still be 5 seconds.&lt;/p>
&lt;p>Another example you may have used is a &lt;code>Query Builder&lt;/code>. For example the &lt;a href="https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/reference/query-builder.html" target="_blank" rel="noopener">Doctrine Query builder&lt;/a>.
The query builder, as the name suggests, allows you to build a query. One of the strengths of this is that you can have conditionals, like so:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">getUsers&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">EntityManager&lt;/span> &lt;span class="nv">$em&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bool&lt;/span> &lt;span class="nv">$onlyActive&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$qb&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$em&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">createQueryBuilder&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$qb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">select&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;u&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">from&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;User&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;u&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$onlyActive&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$qb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">where&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;active = 1&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$qb&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getQuery&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getArrayResult&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now at runtime you can determine if you want only the active users, or all users. And
you don&amp;rsquo;t need to do any magic like concat strings together so the sql is valid.&lt;/p>
&lt;h2 id="why-use-it">Why use it&lt;/h2>
&lt;p>A strength of the builder pattern is that it allows you to build up an object bit by bit.
But, because you do this before you actually create the object, the end result can be immutable. Meaning you wont need setters on the object.
Immutable objects are generally easier to debug and reason about.&lt;/p>
&lt;p>The builder object itself is mutable by nature, its sole intention is to be dynamically changed.
But, the builder object is usually shot lived, while the resulting object may be used in a lot of places.&lt;/p>
&lt;h2 id="in-conclusion">In conclusion&lt;/h2>
&lt;p>The builder pattern is a creational pattern, that allows you to build an object dynamically.
It can help in making objects immutable. It can also help make the configuration of an object
easier to understand, as it can be done in steps.&lt;/p>
&lt;h2 id="other-posts-in-this-series">Other posts in this series&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-decorator/">Decorator&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-adapter/">Adapter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-factory/">Factory&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-builder/">Builder&lt;/a>&lt;/li>
&lt;/ol></description></item><item><title>Every day design pattern: Factory</title><link>https://backendtea.com/post/every-day-design-pattern-factory/</link><pubDate>Mon, 25 Jan 2021 19:58:22 +0100</pubDate><guid>https://backendtea.com/post/every-day-design-pattern-factory/</guid><description>&lt;p>The previous two chapter of this blog series were about the &lt;a href="https://backendtea.com/post/every-day-design-pattern-decorator/">decorator&lt;/a> and the &lt;a href="https://backendtea.com/post/every-day-design-pattern-adapter/">adapter&lt;/a>.
These are structural design patterns. Meaning they deal with the structure of a system.
They can help with simplifying relationships, and moving responsibilities.&lt;/p>
&lt;p>The factory is a completely different type of design pattern. Its a &lt;strong>creational pattern&lt;/strong>.
A creation pattern is intended to make creation of objects easier.
I generally use one when a class needs a multiple parameters,
but only a few need to be &amp;lsquo;dynamic&amp;rsquo;. Lets take a look at an example.&lt;/p>
&lt;p>Lets say we have SaaS where multiple companies use our system. They all have their own accounts. So we have an account service, which takes an account repository, but also the
id of the current company. When we create the account service, we need to know this id, but
we don&amp;rsquo;t really care about the repository it uses.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">interface&lt;/span> &lt;span class="nx">AccountRepository&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">findByEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$email&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$companyId&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="o">?&lt;/span>&lt;span class="nx">Account&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">AccountService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">AccountRepository&lt;/span> &lt;span class="nv">$repository&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$companyId&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getAccountByEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$email&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Account&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$account&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">repository&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">findByEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">companyId&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$account&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="nx">AccountNotFoundException&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">fromEmail&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$email&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$account&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>When we use a factory to create this service it would look like this. When creating the object we need to know the company id. It may be a query parameter, or we know it because of
the url that is visited. Whatever the case, we only know this at runtime, and can&amp;rsquo;t do this
beforehand in a configuration or something like that.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">AccountServiceFactory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">AccountRepository&lt;/span> &lt;span class="nv">$repository&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">create&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$companyId&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">AccountService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">AccountService&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">repository&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$companyId&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Another reason to create a factory, may be to simplify creation. This is especially useful
when you provide a package. Lets look at react-php. They provide a static factory class,
which is used to create a &lt;code>LoopInterface&lt;/code>. Which one you can use is depending on installed
extension, and php versions. The class is even called &lt;a href="https://github.com/reactphp/event-loop/blob/8bd064ce23c26c4decf186c2a5a818c9a8209eb0/src/Factory.php" target="_blank" rel="noopener">Factory&lt;/a>&lt;/p>
&lt;p>An excerpt of the class looks like this. When you need a LoopInterface, all you have to do
is &lt;code>Factory::create()&lt;/code>. It means you don&amp;rsquo;t have to worry about having the correct extensions
ready, just to get started.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">Factory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">create&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">function_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;uv_loop_new&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">ExtUvLoop&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">elseif&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">class_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;libev\EventLoop&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// more elseifs
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">StreamSelectLoop&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="when-not-to-use-a-factory">When not to use a factory&lt;/h2>
&lt;p>Sometimes, i see factories being used when they don&amp;rsquo;t add any ease of use:&lt;/p>
&lt;p>An example may be the following factory:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">UserFactory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">newUser&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$email&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$passwordHash&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">User&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$email&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$passwordHash&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here the factory doesn&amp;rsquo;t make instantiation of the user any easier, instead it just adds
another step before the creation of the object.&lt;/p>
&lt;h2 id="in-conclusion">In conclusion&lt;/h2>
&lt;p>The factory pattern is used to make creation of objects easier. Either by deciding which one
to create, or by splitting up when certain parameters are required.&lt;/p>
&lt;h2 id="other-posts-in-this-series">Other posts in this series&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-decorator/">Decorator&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-adapter/">Adapter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-factory/">Factory&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-builder/">Builder&lt;/a>&lt;/li>
&lt;/ol></description></item><item><title>Every Day Design Pattern: Adapter</title><link>https://backendtea.com/post/every-day-design-pattern-adapter/</link><pubDate>Sun, 10 Jan 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/every-day-design-pattern-adapter/</guid><description>&lt;p>This is the second post in a series of design patterns i use (almost) daily.
You will find the other posts at the bottom of this article.&lt;/p>
&lt;p>On &lt;a href="https://en.wikipedia.org/wiki/Adapter_pattern" target="_blank" rel="noopener">wikipedia&lt;/a>, the adapter pattern is described like so:&lt;/p>
&lt;blockquote>
&lt;p>the adapter pattern is a software design pattern (&amp;hellip;) that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.&lt;/p>&lt;/blockquote>
&lt;p>The adapter is used to make a class &amp;lsquo;fit&amp;rsquo; the interface you wish to use. Which can be
helpful when you have multiple implementations of something, or if you want to migrate from
one system to the other.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="multiple-implementations">Multiple implementations&lt;/h2>
&lt;p>A great example of using adapters to have multiple implementations is &lt;a href="https://github.com/thephpleague/flysystem/tree/1d68c2325a56b6b847f3a40b9564cabfa7bb2594" target="_blank" rel="noopener">Flysystem&lt;/a>.
Flysystem will let you save to different kinds of backends, without having to change your code. There is one interface you talk to, and behind that can be any &lt;strong>adapter&lt;/strong> which saves
it to a specific place, like dropbox, aws S3, or your local filesystem.&lt;/p>
&lt;p>For that it has the &lt;a href="https://github.com/thephpleague/flysystem/blob/1d68c2325a56b6b847f3a40b9564cabfa7bb2594/src/FilesystemAdapter.php" target="_blank" rel="noopener">&lt;code>FileSystemAdapter&lt;/code>&lt;/a> interface. This is the interface every implementation has to adhere to.
Then there is a &lt;a href="https://github.com/thephpleague/flysystem/blob/1d68c2325a56b6b847f3a40b9564cabfa7bb2594/src/Local/LocalFilesystemAdapter.php" target="_blank" rel="noopener">&lt;code>LocalFileSystemAdapter&lt;/code>&lt;/a> which implements this interface. Or the &lt;a href="https://github.com/thephpleague/flysystem/blob/1d68c2325a56b6b847f3a40b9564cabfa7bb2594/src/Ftp/FtpAdapter.php" target="_blank" rel="noopener">&lt;code>FtpAdapter&lt;/code>&lt;/a>, which also does that.&lt;/p>
&lt;p>Now, when you want to save to FTP on your production server, and to the local file system on dev, the only thing you&amp;rsquo;ll need to change is some configuration, to make sure you get the
right class. Now when you decide to switch from FTP to google cloud, you use the &lt;a href="https://github.com/thephpleague/flysystem/blob/1d68c2325a56b6b847f3a40b9564cabfa7bb2594/src/GoogleCloudStorage/GoogleCloudStorageAdapter.php" target="_blank" rel="noopener">&lt;code>GoogleCloudStorageAdapter&lt;/code>&lt;/a>.&lt;/p>
&lt;p>Here using adapters for the same interface allows you more flexibility, and makes upgrading easier.&lt;/p>
&lt;h2 id="migrating-between-systems">Migrating between systems&lt;/h2>
&lt;p>For this example i want to take you back to october 2017. During that time i was working a bit on &lt;a href="https://github.com/opencfp/opencfp" target="_blank" rel="noopener">OpenCFP&lt;/a>. OpenCFP used a deprecated library for its authentication: sentry.
The idea was to replace sentry with sentinel, a new auth package by the same people that made sentry.&lt;/p>
&lt;p>This was done in two steps. The first step was done by &lt;a href="https://github.com/mdwheele" target="_blank" rel="noopener">Dustin Wheeler&lt;/a>. In &lt;a href="https://github.com/opencfp/opencfp/pull/501/files" target="_blank" rel="noopener">this pull request&lt;/a>, all usages of the sentry authentication where &amp;lsquo;hidden&amp;rsquo; behind two interfaces. The &lt;code>Authentication&lt;/code> and &lt;code>AccountManagement&lt;/code> interfaces. This meant that the only places in the code base that knew about sentry were the implementations of those interfaces.&lt;/p>
&lt;p>So now there existed the &lt;a href="https://github.com/mdwheele/opencfp/blob/f93455aefa398e38b5c3fb6408a6ad4ba76376b5/classes/Infrastructure/Auth/SentryAuthentication.php" target="_blank" rel="noopener">SentryAuthentication&lt;/a> and &lt;a href="https://github.com/mdwheele/opencfp/blob/f93455aefa398e38b5c3fb6408a6ad4ba76376b5/classes/Infrastructure/Auth/SentryAccountManagement.php" target="_blank" rel="noopener">SentryAccountManagement&lt;/a> Only these two places knew of anything related to the authentication/account management. By first moving to this new implementation we made the
migration much easier. All we now needed to do was create new implementations, based on the
new sentinel package, and we were done.&lt;/p>
&lt;p>The sentinel upgrade later came in november 2017 in &lt;a href="https://github.com/opencfp/opencfp/pull/624/files" target="_blank" rel="noopener">this pull request&lt;/a>. In the end it was still 39 files big, but keep in mind that 18 of those were tests, and another 4 were database migrations. This is of course a lot more than just two implementations of the interface. In the end a few more things were needed, to
make sure everything kept working.&lt;/p>
&lt;p>By using the two adapters, we could switch between sentry and sentinel, without having to
change any of the code using it. All of the logic related to the third party code was in the adapter. This also means that if sentinel had to be updated, and some of the logic would change, this only needed to be done in one place.&lt;/p>
&lt;h2 id="other-posts-in-this-series">Other posts in this series&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-decorator/">Decorator&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-adapter/">Adapter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-factory/">Factory&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-builder/">Builder&lt;/a>&lt;/li>
&lt;/ol>
&lt;h2 id="people-related-to-this-post">People related to this post&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://twitter.com/grmpyprogrammer" target="_blank" rel="noopener">Chris Hartjes&lt;/a> - OpenCFP creator/maintainer&lt;/li>
&lt;li>&lt;a href="https://twitter.com/mdwheele" target="_blank" rel="noopener">Dustin Wheeler&lt;/a> - Created the &amp;lsquo;adapter&amp;rsquo; PR in OpenCFP&lt;/li>
&lt;li>&lt;a href="https://twitter.com/frankdejonge" target="_blank" rel="noopener">Frank de Jonge&lt;/a> - Flysystem creator/maintainer&lt;/li>
&lt;/ul></description></item><item><title>Every day design pattern: Decorator</title><link>https://backendtea.com/post/every-day-design-pattern-decorator/</link><pubDate>Fri, 01 Jan 2021 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/every-day-design-pattern-decorator/</guid><description>&lt;p>This is the first post in a series of design patterns i use (almost) daily.
You will find the other posts at the bottom of this article.&lt;/p>
&lt;h2 id="the-decorator-pattern">The Decorator pattern&lt;/h2>
&lt;p>On &lt;a href="https://en.wikipedia.org/wiki/Decorator_pattern" target="_blank" rel="noopener">wikipedia&lt;/a>, the decorator pattern is described like so:&lt;/p>
&lt;blockquote>
&lt;p>In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.&lt;/p>&lt;/blockquote>
&lt;p>Personally, i use this pattern to either add functionality to a class from a
third party package, or to separate the concerns of a class. The decorator does so by
&amp;lsquo;wrapping&amp;rsquo; around the original object.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="adding-functionality">Adding functionality&lt;/h2>
&lt;p>Lets say i&amp;rsquo;m sending emails with the symfony &lt;a href="https://symfony.com/doc/current/mailer.html" target="_blank" rel="noopener">Mailer&lt;/a> component. You can add a header to an email that tells the
email receiver not to send back any automated mails (like an out of office mail).&lt;/p>
&lt;p>Since were sending automated mails, we never want to receive those mails. So we want to add
that to every single one of our emails. We do this by decorating the &lt;code>MailerInterface&lt;/code>.
We take the original mailer as a constructor argument, and then we add our own logic.&lt;/p>
&lt;p>This is how you&amp;rsquo;d normally want to use this design pattern. You take the original class in the constructor, and add your own special logic on top of that.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">final&lt;/span> &lt;span class="k">class&lt;/span> &lt;span class="nc">DecoratedMailer&lt;/span> &lt;span class="k">implements&lt;/span> &lt;span class="nx">MailerInterface&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">MailerInterface&lt;/span> &lt;span class="nv">$inner&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">RawMessage&lt;/span> &lt;span class="nv">$message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Envelope&lt;/span> &lt;span class="nv">$envelope&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// RawMessage doesn&amp;#39;t have headers, so we can only do this if we have a `Message`
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$message&lt;/span> &lt;span class="nx">instanceof&lt;/span> &lt;span class="nx">Message&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$message&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getHeaders&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">addTextHeader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;X-Auto-Response-Suppress&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;OOF, DR, RN, NRN, AutoReply&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">inner&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">send&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$envelope&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="separation-of-concerns">Separation of concerns&lt;/h3>
&lt;p>A decorator can also be used to separate the concerns of a class. Lets say we have the following &lt;code>Api&lt;/code> interface, and its implementation. This class does too much, and its hard(er) to maintain or add features to. What if we wanted to not cache certain requests?&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">interface&lt;/span> &lt;span class="nx">Api&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyApi&lt;/span> &lt;span class="k">implements&lt;/span> &lt;span class="nx">Api&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">Client&lt;/span> &lt;span class="nv">$guzzle&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">LoggerInterface&lt;/span> &lt;span class="nv">$logger&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">CacheInterface&lt;/span> &lt;span class="nv">$cache&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$apiKey&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cacheKey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$method&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nv">$url&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nx">http_build_query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$params&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cachedResult&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">cache&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$cacheKey&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$cachedResult&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$cachedResult&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">logger&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Requesting %s - %s&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$url&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$params&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">RequestOptions&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">HEADERS&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;api-key&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">apiKey&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">guzzle&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$body&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">$result&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getBody&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">logger&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Response: &amp;#34;%s&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$body&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">cache&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$cacheKey&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$body&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$body&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lets split it up into multiple classes, using the decorator pattern. We turn this into three
classes, the decorator that cares about cache, the decorator that cares about logging, an the
actual class. Now we can easily remove cache for a specific request. Testing also becomes easier. The test for the &lt;code>CachingApi&lt;/code> now only needs to check that items are properly added to cache, or fetched form it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CachingApi&lt;/span> &lt;span class="k">implements&lt;/span> &lt;span class="nx">Api&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">Api&lt;/span> &lt;span class="nv">$inner&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">CacheInterface&lt;/span> &lt;span class="nv">$cache&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cacheKey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$method&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nv">$url&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nx">http_build_query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$params&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$cachedResult&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">cache&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$cacheKey&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$cachedResult&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$cachedResult&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">inner&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">cache&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$cacheKey&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$result&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">LoggingApi&lt;/span> &lt;span class="k">implements&lt;/span> &lt;span class="nx">Api&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">Api&lt;/span> &lt;span class="nv">$inner&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">LoggerInterface&lt;/span> &lt;span class="nv">$logger&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">logger&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Requesting %s - %s&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$url&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">inner&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">logger&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Response: &amp;#34;%s&amp;#34;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$result&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$result&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MyApi&lt;/span> &lt;span class="k">implements&lt;/span> &lt;span class="nx">Api&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">Client&lt;/span> &lt;span class="nv">$guzzle&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$apiKey&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span> &lt;span class="p">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">array&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$params&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nx">RequestOptions&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">HEADERS&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;api-key&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">apiKey&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">guzzle&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">request&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$method&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">$result&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getBody&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="no-interface-no-problem">No interface, no problem&lt;/h2>
&lt;p>The examples so far have used interfaces. Sometimes the class we wish to decorate does not
have an interface. In that case we need to extend the original class. You now have the option to either call the parent directly, or inject the class in the constructor. Personally
i would go with injecting the class in the constructor, as this allows you to have multiple decorators on top of each other.&lt;/p>
&lt;h3 id="the-problem">The problem&lt;/h3>
&lt;p>So i lied a little bit, as there can be a problem when using a decorator when you extend a class. And that is with fluid interfaces. A fluid interface is a class that returns itself on
(almost) every method call. Like the following &lt;code>Request&lt;/code> class.&lt;/p>
&lt;p>We could decorate this no problem, and do the following.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Request&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">setUrl&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">setMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">method&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">EchoRequest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">Request&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">setUrl&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">echo&lt;/span> &lt;span class="nv">$url&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">inner&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">setUrl&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$url&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">setMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">echo&lt;/span> &lt;span class="nv">$method&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">inner&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">setMethod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$method&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>But, what happens if the request class gets updated, and a the following method gets added.
Suddenly when you call &lt;code>setParams&lt;/code> on the decorated class, you are working with the non decorated version. So be careful when using the decorator pattern in combination with fluid interfaces.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Request&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">//...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">setParams&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$params&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="the-takeaway">The takeaway&lt;/h2>
&lt;p>The decorator patterns allows you to add additional functionality to a class, or it
can help in splitting up the responsibilities of a class.
You may want to be careful when
decorating an object that has a fluid interface, as you can suddenly use the non decorated class.&lt;/p>
&lt;h2 id="other-posts-in-this-series">Other posts in this series&lt;/h2>
&lt;ol>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-decorator/">Decorator&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-adapter/">Adapter&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-factory/">Factory&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://backendtea.com/post/every-day-design-pattern-builder/">Builder&lt;/a>&lt;/li>
&lt;/ol></description></item><item><title>Replacing private services with mocks during tests</title><link>https://backendtea.com/post/test-private-symfony-services/</link><pubDate>Fri, 18 Dec 2020 15:49:00 +0100</pubDate><guid>https://backendtea.com/post/test-private-symfony-services/</guid><description>&lt;p>Recently i wanted to create an E2E test for a project i was working on.
However, for some of its data, the test would talk to some remote API, to fetch data.
Which i didn&amp;rsquo;t want to happen in the test.&lt;/p>
&lt;p>So i wrote the following code, and assumed everything would work:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">createClient&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$repo&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">MemoryUserRepository&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="nv">$container&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">set&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">RemoteUserRepository&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$repo&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$client&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">request&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;POST&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;/users/create&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;CONTENT_TYPE&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">json_encode&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// user data
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>However, i was quickly greeted with the following error. I had tried to replace a private service.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">There was 1 error:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1) App\Tests\E2E\CreateUserTest::testCanCreateUsers
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The &amp;#34;App\Domain\Repository\RemoteUserRepository&amp;#34; service is private, you cannot replace it.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>The &amp;hellip; service is private, you cannot replace it.&lt;/p>&lt;/blockquote>
&lt;p>A quick google told me that i had 3 options, make all my services public, which has its own issues,
or to mark this specific service as public,
or to add a custom version of the &lt;code>RemoteUserRepository&lt;/code> as a service for my test environment.&lt;/p>
&lt;p>The last option would&amp;rsquo;ve been fine, but i wanted to replace it with different mocks, depending on
what i needed for that test. Perhaps i wanted to create a test where i made sure that the remote repository was never called.&lt;/p>
&lt;p>Making this service public wouldn&amp;rsquo;t have been that bad. But it felt weird to change my production container, just for my tests. Thankfully, you can create a &lt;code>services.yaml&lt;/code>,
just for you tests: &lt;code>services_test.yaml&lt;/code>. Another upside of that is that it is clear these changes
are just for your tests.&lt;/p>
&lt;p>So i ended up with the following &lt;code>services_test.yaml&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c">#config/services_test.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">App\Domain\Repository\RemoteUserRepository&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">public&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>If you want to get notified of the next blog post, join the newsletter.&lt;/p>
&lt;div style="text-align: left" class="sender-form-field" data-sender-form-id="lvulgg1be5mxqero4jz">&lt;/div></description></item><item><title>Testing code that generates warnings</title><link>https://backendtea.com/post/phpunit-test-warnings/</link><pubDate>Thu, 21 Nov 2019 20:51:46 +0100</pubDate><guid>https://backendtea.com/post/phpunit-test-warnings/</guid><description>&lt;p>Our code base has a lot of code that looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">doScaryThing&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Exception&lt;/span> &lt;span class="nv">$e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">trigger_error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Downgraded: &amp;#34;&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nx">get_class&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s2">&amp;#34;:&amp;#34;&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nv">$e&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getMessage&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">E_USER_WARNING&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Or sometimes &lt;code>trigger_error&lt;/code> is used as a way to log other thigns. This makes it rather difficult
to test. Thankfully PHPUnit 8.4 has the &lt;code>expectWarning&lt;/code> method, that allows us to check this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItTriggersWarning&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$object&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Danger&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">expectWarning&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$object&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">doWarningThing&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This does mean that the execution stops after the warning is triggered, so we can&amp;rsquo;t assert anything after
that. Thankfully we can still do it in a bit of a &amp;lsquo;hackish&amp;rsquo; way:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">testItHasCorrectValueAfterWarning&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">void&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$object&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Danger&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$ret&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">@&lt;/span>&lt;span class="nv">$object&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">doWarningThing&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;good_value&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$ret&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Personally i like to add an &lt;code>@depends&lt;/code> on that second test, so that it requires the test that it triggers
a warning to pass before it is executed. Of course this does mean we need two tests per case, one for the
triggering of the warning/notice, and one where it is silenced so we can check the output. But it allows
us to test the code, and maybe move to a better logging situation in the future.&lt;/p></description></item><item><title>What is the boy scout rule</title><link>https://backendtea.com/post/boyscout-rule/</link><pubDate>Mon, 11 Nov 2019 12:40:49 +0100</pubDate><guid>https://backendtea.com/post/boyscout-rule/</guid><description>&lt;p>The boy scouts have a rule:&lt;/p>
&lt;blockquote>
&lt;p>Always leave the campsite better than you found it&lt;/p>&lt;/blockquote>
&lt;p>This means they throw away the trash they see, they clean up the area, and at the
very least, they don&amp;rsquo;t make it any worse.&lt;/p>
&lt;p>We can apply this same rule to our code base. Always leave the code cleaner or better than
you found it. Over a longer period of time, consistently applying this rule can have
great impact on our code base.&lt;/p>
&lt;p>But what does the boy scout rule mean in the context of our code? For a camp site its simple
to see what can be done. For our code this may prove a bit more difficult. Just recently i spoke
with someone who said they couldn&amp;rsquo;t apply the boy scout rule to their code, as they don&amp;rsquo;t have
the time for it. When investigating a bit further, it turned out they mis understood the boy scout rule.
To them it meant making every piece of code you touch perfect. And all the code that it interacts with
as well. This is for many code bases close to impossible, and its also not what the boy scout rule is.&lt;/p>
&lt;p>The boy scout rule is about making small, continuous improvements to your code. If you are fixing a bug,
and find the code around it is a mess, fix a bit of that mess. You don&amp;rsquo;t have to make it perfect, you
don&amp;rsquo;t have to fix it all, but improve it just a little bit.&lt;/p>
&lt;p>Lets look at an example. Recently i found a factory class that was instantiating an object with more
parameters than it needed. (Which is totally valid and not a problem for PHP) So i decided to fix that
issue.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>The starting code looked something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ImplementationFactory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nv">$container&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Container&lt;/span> &lt;span class="nv">$container&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">container&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$container&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @return ImplementationInterface
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">createImplementation&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">container&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Config&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$router&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">container&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Router&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$config&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getEnv&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;development&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">DevImplementation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$router&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Implementation&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$router&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>There are a few things wrong with this code. The one i was initially going to fix was the
fact that these constructors no longer took arguments. But there are more:&lt;/p>
&lt;ol>
&lt;li>The factory takes the complete container as an argument.&lt;/li>
&lt;li>We are using an else when it is not needed.&lt;/li>
&lt;li>We are checking the env against a string, where a constant may be more appropriate&lt;/li>
&lt;/ol>
&lt;p>And after all that, its also the question of whether we should have a different dev implementation
for this, and whether that problem should be solved in this factory.&lt;/p>
&lt;p>Lets first of all start with the task i was supposed to do:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl"> public function createImplementation()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> $config = $this-&amp;gt;container-&amp;gt;get(Config::class);
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- $router = $this-&amp;gt;container-&amp;gt;get(Router::class);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if ($config-&amp;gt;getEnv() === &amp;#39;development&amp;#39;) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- return new DevImplementation($config, $router);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ return new DevImplementation();
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> } else {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- return new Implementation($config, $router);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ return new Implementation();
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>At this point i could close the ticket, and move on. But i apply the boy scout rule
I got three points that are &lt;em>wrong&lt;/em> with the code.
Lets fix the first one: &amp;ldquo;The factory takes the complete container as an argument.&amp;rdquo;
This isn&amp;rsquo;t necessarily wrong, but it is far more than what this factory needs to know:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">class ImplementationFactory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- private $container;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ private $config
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- public function __construct(Container $container)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ public function __construct(Config $config)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- $this-&amp;gt;container = $container;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ $this-&amp;gt;config = $config;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> /**
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> * @return ImplementationInterface
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> */
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> public function createImplementation()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- $config = $this-&amp;gt;container-&amp;gt;get(Config::class);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- if ($config-&amp;gt;getEnv() === &amp;#39;development&amp;#39;) {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ if ($this-&amp;gt;config-&amp;gt;getEnv() === &amp;#39;development&amp;#39;) {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> return new DevImplementation();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> } else {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return new Implementation();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is a bit better already. At this point i run the test suite, to see where i broke something.
To my surprise, no tests broke. (This wasn&amp;rsquo;t that much of a surprise, as there are very little tests.)&lt;/p>
&lt;p>So instead of moving on the the next point i added 2 tests. One to check that for development the
&lt;code>DevImplementation&lt;/code> was returned, and another one to make sure in other cases the &lt;code>Implementation&lt;/code> was
returned.&lt;/p>
&lt;p>At this point i had spent less than 10 extra minutes on this code. I found it safe to take the 30 extra
seconds to remove the redundant else statement. And while we are at it, lets replace the return annotation with
a proper return type&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- /**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- * @return ImplementationInterface
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- */
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- public function createImplementation()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ public function createImplementation(): ImplementationInterface
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> if ($this-&amp;gt;config-&amp;gt;getEnv() === &amp;#39;development&amp;#39;) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return new DevImplementation();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- } else {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- return new Implementation();
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ return new Implementation();
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, the &lt;code>Config&lt;/code> class lives in another package, so adding constants to hold the environment
strings would involve adding it in that package, and updating that. Although it wouldn&amp;rsquo;t be too
much extra time, this was enough for now. And whether or not this was even the correct place for this
was also outside of the scope for todays work.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ImplementationFactory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">private&lt;/span> &lt;span class="nv">$config&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__construct&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Config&lt;/span> &lt;span class="nv">$config&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$config&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">createImplementation&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">ImplementationInterface&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">config&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getEnv&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;development&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">DevImplementation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Implementation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So in the span of maybe 15 minutes, i fixed what i originally set out to do, cleaned up
the class a bit, and added unit tests. I know that there are still issues
with this piece of code, and probably with the code that uses this class. So while this
code isn&amp;rsquo;t perfect as of right now, it is better than how i found it. And that is what the
boy scout rule is about.&lt;/p>
&lt;p>Now, this was piece of code was relatively easy to refactor. I could see what changes
could be made without too much work. Other times this may not be as simple. The key
is to make improvements that can be made within a small time frame, and that are relatively
easy to do.&lt;/p>
&lt;p>One of the first improvements i would suggest to any piece of code is adding tests. Having tests
to validate the behaviour of your code makes it easier to make changes. And even during that,
remember the boy scout rule. 100% coverage may be too high of a goal for a specific class or method.
But we can at least make the first step. And then the next time, we can improve that coverage a bit more.&lt;/p>
&lt;p>Now if you consistently apply this rule, over time you can make big improvements. Initially
the changes are small. But if a lot of small changes add up. And after while, you may even be
able to make bigger changes, because the code is clean and understandable enough for it. And
before you know it, the small change you apply, does make the code &amp;lsquo;perfect&amp;rsquo;.&lt;/p></description></item><item><title>When to add comments</title><link>https://backendtea.com/post/when-to-add-comments/</link><pubDate>Thu, 31 Oct 2019 21:16:05 +0100</pubDate><guid>https://backendtea.com/post/when-to-add-comments/</guid><description>&lt;p>A lot of methods have comments, either explaining what they do, how they do it, why they do it.
And a lot of times they are wrong. More often than not, you don&amp;rsquo;t need to add comments, so
lets take a look at when and why comments are useful.&lt;/p>
&lt;h2 id="when-we-do-add-comments">When we do add comments&lt;/h2>
&lt;p>A valid reason to add a comment may be one of the following:&lt;/p>
&lt;ol>
&lt;li>It expresses something the language can&amp;rsquo;t&lt;/li>
&lt;li>It is needed for certain tools&lt;/li>
&lt;li>It is a non obvious work around.&lt;/li>
&lt;/ol>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h3 id="it-expresses-something-the-language-cant">It expresses something the language can&amp;rsquo;t&lt;/h3>
&lt;p>If we take the following method signature as an example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">combine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We don&amp;rsquo;t know what the input array is supposed to look like. Does it expect an array
of integers, strings, certain objects? This is something we can&amp;rsquo;t express in PHP itself right now.
But if we write it down like so, we get a better understanding:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param array&amp;lt;int&amp;gt; $input
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">combine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">array&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now you may be more used to seeing &lt;code>int[]&lt;/code> as the type hint. the &lt;code>array&amp;lt;T&amp;gt;&lt;/code> is the same.
But the difference is that you can also say &lt;code>array&amp;lt;int,int&amp;gt;&lt;/code> as a comment, which means that you
expect the keys to be integers.&lt;/p>
&lt;p>Another thing the language doesn&amp;rsquo;t support &lt;em>yet&lt;/em> is union types, so a parameter taking an array or a string
can&amp;rsquo;t be expressed yet, but with a comment we can say &lt;code>@param array|string&lt;/code>. And our IDE and tooling will
understand, as well as our colleagues (hopefully).&lt;/p>
&lt;p>You may also be using a library like &lt;a href="https://www.doctrine-project.org/projects/annotations.html" target="_blank" rel="noopener">Doctrine Annotations&lt;/a> to add even more functionality, through annotations.&lt;/p>
&lt;h3 id="it-is-needed-for-certain-tools">It is needed for certain tools&lt;/h3>
&lt;p>Now i will preface this by saying that i am not a fan of littering your code base with comments
that are used to instruct tools. For example &lt;code>@codeCoverageIgnore&lt;/code> or &lt;code>@codingStandardsIgnore&lt;/code> shouldn&amp;rsquo;t
be in your code base, but they should be part of a config. However, with annotations we can support
certain tools to help us with validating our code. For example, we can add great support for verifying
&lt;a href="https://psalm.dev/articles/immutability-and-beyond" target="_blank" rel="noopener">immutability&lt;/a> by using
&lt;a href="https://github.com/vimeo/psalm" target="_blank" rel="noopener">Psalm&lt;/a> and the &lt;code>@psalm-immutable&lt;/code> annotation.&lt;/p>
&lt;h3 id="it-is-a-non-obvious-work-around">It is a non obvious work around.&lt;/h3>
&lt;p>This is the &amp;lsquo;real&amp;rsquo; reason you want to use comments. You may be doing something that
makes very little sense, because you are facing a bug that you can not fix.&lt;/p>
&lt;p>For example, a project i worked on could be used by using only the keyboard.
However, we ran into a bug on firefox where a &lt;code>e.preventDefault()&lt;/code> didn&amp;rsquo;t stop a
dropdown from still being changed when an arrow key was pressed. The only way to
do so was to disable the element. So that is what we did. But if someone would
see the code, their first thought may be that it isn&amp;rsquo;t needed. (Because it isn&amp;rsquo;t on other browsers).&lt;/p>
&lt;p>So we added a comment to the method:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-js" data-lang="js">&lt;span class="line">&lt;span class="cl">&lt;span class="cm">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * We temporary disable the selector, so the pressing of arrow keys doesn&amp;#39;t change the rating given.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * This is needed for firefox, as the event.preventDefault() does not disable the arrow keys on selectors.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> * @see https://bugzilla.mozilla.org/show_bug.cgi?id=291082
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span> &lt;span class="nx">temporaryDisableSelector&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Looking back this may not be the greatest comment. Part of it just copies the function name, but
it does explain why it is there, and even has a link to a bug tracker.&lt;/p>
&lt;h2 id="when-we-dont-add-comments">When we don&amp;rsquo;t add comments&lt;/h2>
&lt;p>TL;DR: anything else.&lt;/p>
&lt;p>A lot of comments fall in the following categories:&lt;/p>
&lt;ol>
&lt;li>It duplicates the code&lt;/li>
&lt;li>It duplicates VCS&lt;/li>
&lt;li>It is wrong&lt;/li>
&lt;/ol>
&lt;h3 id="it-duplicates-the-code">It duplicates the code&lt;/h3>
&lt;p>Far too often i see comments like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * CheckoutController class
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @package \Website\Checkout
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CheckoutController&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>When this exact comment is on every single class in your code base, people ignore it.
Now the one time you do actually have something important to say in your comment, no one
is going to read it. Because everywhere else they can ignore those 5 lines, so why start
reading them now.&lt;/p>
&lt;p>Another one i see a lot looks something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param int $count
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string $name
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param Order $order
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param $other
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">doTheMagic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$count&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">string&lt;/span> &lt;span class="nv">$name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Order&lt;/span> &lt;span class="nv">$order&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$other&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">bool&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This comment does absolutely nothing, besides duplicating the method signature. Once again,
if this is part of every doc comment, people will never read it, and when there is something there
people will not see it.&lt;/p>
&lt;p>Sometimes the comment may appear to be relevant, but they still aren&amp;rsquo;t:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$price&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$order&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nv">$order&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$order&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">hasPrice&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$price&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$order&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getPrice&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Stop after we find a price
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In this case, the comment just tells you what the break tells you. You are repeating
the code in a comment. The danger here is that when your code updates, the
comment may be wrong. But we don&amp;rsquo;t know for sure.&lt;/p>
&lt;p>For example, a few months later it may look like this, and then our comment isn&amp;rsquo;t always
true, and it is also in the wrong spot.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$price&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$order&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nv">$order&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$order&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">hasPrice&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$price&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="nv">$order&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getPrice&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Stop after we find a price
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nv">$price&lt;/span> &lt;span class="o">*=&lt;/span> &lt;span class="nv">$order&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getDiscount&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">combinePrices&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="it-duplicates-vcs">It duplicates VCS&lt;/h3>
&lt;p>Far too many projects are filled with &lt;code>@author&lt;/code> annotations, or the default PHPStorm annotations
when you create a new file, where the creation date is in the comment. Those don&amp;rsquo;t add any value,
as the information can be retrieved from your version control.&lt;/p>
&lt;h3 id="it-is-wrong">It is wrong&lt;/h3>
&lt;p>This is the danger with any comment. Generally comments don&amp;rsquo;t start out wrong,
but the code around them updates. And the comment doesn&amp;rsquo;t get updated to match.
I gave an example of this in the part about comments that duplicate code.&lt;/p>
&lt;p>One i saw recently looked somethign like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * Retrieves the information as follows:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * 1. From the cache
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * 2. Otherwise from redis
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * 3. Otherwise it checks the config file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * 4. From the database
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * 5. Or an empty string as a fallback
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">getQuickData&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">sql&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getFromInput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$input&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">??&lt;/span> &lt;span class="s1">&amp;#39;Error&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now looking back in the VCS, the comment wasn&amp;rsquo;t even correct when it was added, as the config
file was never checked. But over time the method was refactored multiple times and the system
changed, and now it just retrieves from the database.&lt;/p>
&lt;p>But if we just check the comments when we peek at the method, we thing we may retrieve from cache, and
we need to check for an empty string.&lt;/p>
&lt;h2 id="in-conclusion">In conclusion&lt;/h2>
&lt;p>There are valid reasons for comments, but more then likely, your comments aren&amp;rsquo;t adding any value.
And the more useless comments there are, the less likely the important comments are to stand out.
So do consider removing comments that don&amp;rsquo;t add anything. Personally i am a fan of the
&lt;a href="https://github.com/doctrine/coding-standard" target="_blank" rel="noopener">&lt;code>Doctrine coding standard&lt;/code>&lt;/a> as one of its rules
gets rid of useless comments for you.&lt;/p>
&lt;p>Now there are more reasons to add, or not add comments to your code base. What reasons do you have
for adding comments, or getting rid of them? And what are some examples of really good, or really
bad comments you have seen? Do let me know in the comments.&lt;/p></description></item><item><title>PHP is already strictly typed</title><link>https://backendtea.com/post/php-is-strictly-typed/</link><pubDate>Fri, 16 Aug 2019 13:25:13 +0200</pubDate><guid>https://backendtea.com/post/php-is-strictly-typed/</guid><description>&lt;p>PHP is already strictly typed, in the same way that JavaScript is. Not through the language itself, but
with the help of tooling.&lt;/p>
&lt;p>JavaScript achieves this through tools like Typescript. (I use typescript as an example, as that is what i normally use.)
Typescript adds a lot of new syntax to tha language, which allows for type checking. The transpiler then simply won&amp;rsquo;t transpile
if there are type errors( depending on your configuration). Your code doesn&amp;rsquo;t do these checks during runtime, so technically you could still
create errors by not using the transpiler and writing the JS code directly.&lt;/p>
&lt;p>PHP achieves its strict typing through tools like &lt;a href="https://github.com/vimeo/psalm" target="_blank" rel="noopener">psalm&lt;/a> and &lt;a href="https://github.com/phpstan/phpstan" target="_blank" rel="noopener">phpstan&lt;/a>. For PHP this doesn&amp;rsquo;t mean new syntax and code being transpiled however.
Since for PHP transpiling the code isn&amp;rsquo;t something that is done often. There are tools for it, but its generally not that popular. However, PHP devs
do love their annotations. A doc comment with &lt;code>@var Foo\Bar&lt;/code> here, another one with &lt;code>@return string&lt;/code> there and so on. With previously mentioned tooling
we can have additional annotations. We can even have &lt;code>@template&lt;/code> annotations to get &lt;a href="https://psalm.dev/docs/annotating_code/templated_annotations/" target="_blank" rel="noopener">generics&lt;/a>.&lt;/p>
&lt;p>Just like typescript, there is no run time validation, and because the code isn&amp;rsquo;t transpiled either, there is no way to stop someone from writing bad code.
The only way to stop this is to make the success of these tools mandatory. This means running them in CI, and not allowing a merge to happen unless these tools
run successfully.&lt;/p>
&lt;p>We don&amp;rsquo;t need a new language/dialect like P++, we can already increase type safety by using the tooling created for that.
So if you want more type safety in your code, and find errors before running the code, use these tools that are already available. If you don&amp;rsquo;t want to use these
tools, then you don&amp;rsquo;t have to. And i think that is the PHP way.&lt;/p></description></item><item><title>The modern PHP developers toolbox</title><link>https://backendtea.com/post/modern-php-toolkit/</link><pubDate>Fri, 26 Apr 2019 21:03:21 +0200</pubDate><guid>https://backendtea.com/post/modern-php-toolkit/</guid><description>&lt;p>There are a lot of tools available to a developer these days. Some are well known, but there are also some diamonds in the rough.
So today, we are gonna look at some of the tools a modern (PHP) developer should use. A lot of these tools aren&amp;rsquo;t specific to PHP,
so even if you are using another language, this may be worth checking out.&lt;/p>
&lt;h2 id="dependency-manager">Dependency manager&lt;/h2>
&lt;p>I&amp;rsquo;d like to think that most projects these days use &lt;a href="https://getcomposer.org/" target="_blank" rel="noopener">composer&lt;/a> these days.
It has been the standard for PHP development for the past few years. Managing your dependencies is easy now!&lt;/p>
&lt;p>A problem before composer what that you had to include all 3rd party code manually. And if that code has a dependency on another
package you had to include that as well. Then if multiple package depended on the same package you needed to make sure you only
had 1 version of that package, and the correct one. Composer does all of that for you through your &lt;code>composer.json&lt;/code> in the project.&lt;/p>
&lt;p>This file specifies the version ranges your project (or package) can work with, and then installs the best versions of all packages
according to all the constraints that each package has. Doing so produces a &lt;code>composer.lock&lt;/code> file as well, which &amp;rsquo;locks&amp;rsquo; you into
the specific dependency versions you install. This means that wherever you install this project, as long as you have the
same &lt;code>composer.lock&lt;/code>, the same dependencies get installed.&lt;/p>
&lt;p>Now if newer versions of the packages you use get published, you can run &lt;code>composer update&lt;/code> to update to the latest versions. You can
do so locally, test the changes, and then you only have to push the new &lt;code>composer.lock&lt;/code> in order for the newer versions to install on production.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="code-style-fixer">Code style fixer&lt;/h2>
&lt;p>I&amp;rsquo;d like to note that i&amp;rsquo;m talking about a fixer here, not a linter. There is a small difference, but to me the result is huge.
A linter will simply tell you all the errors you have, a fixer will fix then (automatically) for you.&lt;/p>
&lt;p>One thing that annoys me to no end is a tool that tells me a bracket should be on a new line. If the tool knows that the bracket
is in the wrong position, and it knows the correct position, it should change it for me. There should also be the option
to run it without actually fixing the issues, to be used in a CI environment for example.&lt;/p>
&lt;p>The fact that &lt;a href="https://github.com/FriendsOfPHP/PHP-CS-Fixer" target="_blank" rel="noopener">PHP-CS-fixer&lt;/a> only has &amp;lsquo;rules&amp;rsquo; that it can automatically fix,
makes it my favorite fixer. Every error it will tell you about is one it can fix automatically.
Do you want to adhere to &lt;a href="https://www.php-fig.org/psr/psr-2/" target="_blank" rel="noopener">PSR-2&lt;/a>? simply add the &lt;code>@PSR2&lt;/code> rule
to the configuration, and the next time your the fixer, your code will be &lt;code>PSR-2&lt;/code> styled. The fact that every issue also needs to be fixable
does make it more limited in what it can detect.&lt;/p>
&lt;p>Another great fixer is &lt;a href="https://github.com/squizlabs/PHP_CodeSniffer" target="_blank" rel="noopener">PHP_CodeSniffer&lt;/a>. It comes with 2 commands: &lt;code>phpcs&lt;/code> and &lt;code>phpcbf&lt;/code>.
The first command, &lt;code>phpcs&lt;/code> which finds all errors and reports them to you. The second command is &lt;code>phpcbf&lt;/code>,
which fixes all the errors it can, and reports the other errors to you.
This means that it can be a fixer for all the issues it can solve, but still allow more complex rules, that can&amp;rsquo;t be automatically fixed.
If you want a strong set of rules I&amp;rsquo;d suggest the &lt;a href="https://www.doctrine-project.org/projects/coding-standard.html" target="_blank" rel="noopener">Doctrine coding standard&lt;/a>.
These are a set of rules, used in the doctrine projects, that uses &lt;code>PSR-2&lt;/code> as its baseline, but adds a whole lot of extra rules that help you
write maintainable code.&lt;/p>
&lt;h2 id="static-analyzer">Static analyzer&lt;/h2>
&lt;p>The previously mentioned tools aren&amp;rsquo;t &amp;lsquo;modern&amp;rsquo; per se, but they are still ever evolving.
Static analysis however, is relatively new in the PHP-scene.
PHPStan saw its initial release on 2016-07-16. PHAN saw its first release on 2015-12-03. And Psalm became open source on 2016-11-21.&lt;/p>
&lt;p>Static analysis is in essence a type checker. But with the &lt;strong>big&lt;/strong> benefit, of not having to run the code to find out if it works.
This means that in order to use a static analysis tool, you don&amp;rsquo;t have to write tests to execute the code. Take a look at the following example:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">sayHello&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;hello&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @return int
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">sayGoodbye&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;good bye&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now the first function will crash the moment it runs. The second function however, will run normally without crashing.
Except that you think you have an integer, when in reality you are dealing with a string.
Running a static analyzer, both of these functions will generate an error, without executing any line of code.
And because it does to without needing to execute(run) the code, it can do so for the entire code base, without needing to write any tests.&lt;/p>
&lt;p>The more you use type hints, whether its in comments or in code, the more static analysis will do for you. Most of these tools work with
levels, where the higher levels will be more strict about your code. For example, it will tell you that your &lt;code>getcwd()&lt;/code> may return false under
specific circumstances, meaning you need to check for that. Static analysis helps you with more defensive programming.&lt;/p>
&lt;p>These days my goto tool is &lt;a href="https://github.com/phpstan/phpstan" target="_blank" rel="noopener">PHPStan&lt;/a>. It has extensions for frameworks,
and allows for easy configuration by using the levels it provides. I have also been using &lt;a href="https://github.com/vimeo/psalm" target="_blank" rel="noopener">Psalm&lt;/a> as of late
which seems to have a lot more options for specifying array/iterable types.
I don&amp;rsquo;t have much experience with &lt;a href="https://github.com/phan/phan" target="_blank" rel="noopener">PHAN&lt;/a>, but its focus lies on reducing false positives,
and has more tooling for checking supported php versions.&lt;/p>
&lt;p>And honorable mention also goes out to &lt;a href="https://exakat.io/" target="_blank" rel="noopener">Exakat&lt;/a>, which has a lot of focus on checking against certain types of php-versions.
It also checks against common mistakes and code smells.&lt;/p>
&lt;p>Static analysis is easy to set up, and the minimum levels are usually not a lot of effort to fix.
Starting with static analysis can give you some quick-wins, and help you write more maintainable, robust code.&lt;/p>
&lt;h2 id="unit-testing-framework">Unit testing framework&lt;/h2>
&lt;p>Yes you need to test your code.&lt;/p>
&lt;p>The biggest php unit testing framework is &lt;a href="https://phpunit.de/" target="_blank" rel="noopener">PHPUnit&lt;/a>. It allows you to set up your test conditions, execute
the code, and then &lt;code>assert&lt;/code> if everything is working correctly.&lt;/p>
&lt;p>Unit testing on is a big topic on its own which will be covered in another upcoming blog-post&lt;/p>
&lt;h2 id="mutation-testing-framework">Mutation testing framework&lt;/h2>
&lt;p>I covered mutation testing, or more specifically &lt;a href="https://github.com/infection/infection" target="_blank" rel="noopener">infection&lt;/a>
in a &lt;a href="https://backendtea.com/post/test-your-tests-are-testing/">previous blog-post&lt;/a>, which I would recommend you to read.
It is rather new, just like static analysis. &lt;a href="https://github.com/humbug/humbug/" target="_blank" rel="noopener">Humbug&lt;/a> saw its first pre release on
2015-05-25. And infection had its 0.1.0 release on 2017-07-01. Humbug has since been deprecated in favor of infection.&lt;/p>
&lt;p>Mutation testing is an extension of unit testing. It is an extra layer on top of your mutation testing that
makes sure your tests are catching the things they need to. Mutation testing is &amp;rsquo;testing your tests&amp;rsquo;.&lt;/p>
&lt;h2 id="editorconfig">Editorconfig&lt;/h2>
&lt;p>Okay, this one isn&amp;rsquo;t really a tool of modern PHP developers, but I like editorconfigs, and its my blog.&lt;/p>
&lt;p>Editorconfigs are a configuration file that you add to (the root of) your project, which tell your editor what kind of
indenting to use where etc. It also tells your IDE whether or not to insert a final new line, clean up trailing white space etc.&lt;/p>
&lt;p>Its configured in an ini like matter, and most IDE&amp;rsquo;s honor it out of the box, but some need a plugin/extension in order to use it.
I&amp;rsquo;d reccomend checking out the &lt;a href="https://editorconfig.org/" target="_blank" rel="noopener">editor config website&lt;/a> to see all the options and how to use it.&lt;/p>
&lt;h2 id="closing-thoughts">Closing thoughts&lt;/h2>
&lt;p>This isn&amp;rsquo;t a full list of the tools you should be using, and some tools may be useless in your project or setting.
I do consider this to be a baseline of tools I would use for any project. I didn&amp;rsquo;t include IDE&amp;rsquo;s or version control like git,
but i do consider those vital.&lt;/p>
&lt;p>Do you have tools you consider vital for development that aren&amp;rsquo;t mentioned here? Or do you disagree with the tools mentioned here?
Let me know by leaving a comment, or by sending me a DM on &lt;a href="https://twitter.com/BackEndTea" target="_blank" rel="noopener">twitter&lt;/a>.&lt;/p></description></item><item><title>What Is Big O</title><link>https://backendtea.com/post/what-is-big-o/</link><pubDate>Tue, 12 Feb 2019 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/what-is-big-o/</guid><description>&lt;p>Big O is used to notate the &amp;rsquo;time and space complexity&amp;rsquo; of algorithms. It provides us with key insights into
how fast an algorithm is, without getting bogged down with details.&lt;/p>
&lt;h2 id="calculating-the-work-of-algorithm">Calculating the work of algorithm&lt;/h2>
&lt;p>Lets start with an example, consider bubblesort, which looks something like this in python:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">bubble_sort&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">arr&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">arr&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Traverse through all array elements&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Last i elements are already in place&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># traverse the array from 0 to n-i-1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Swap if the element found is greater&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># than the next element&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">arr&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">arr&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">arr&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">arr&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">arr&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">arr&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">arr&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It loops over all the elements in the first loop, and then loops over all elements again (save the last &lt;code>i&lt;/code> items),
swapping any element if the next one to it is higher, to make sure the highest entry ends up in the most right position.
Which means that for every item in the array, it loops over the entire array again, save a few items.&lt;/p>
&lt;p>Now how can we express, in numbers now much &amp;lsquo;work&amp;rsquo; this algorithm does, relative to the length of the array, $n$?&lt;/p>
&lt;p>It loops over the entire array once in the first part, and then once again over every item, once more in the inner loop.
For every item in that inner loop, it does a check, and then swaps 2 items if it needs to. So we could say that it does, at most
$3n^2$ work. Now, it does calculate the length at the start of the function, and it does increase j every time, so thats a bit of work too.
Meaning that the work could be expressed as $4 n^2 +1$. And to make this a bit more easy, i&amp;rsquo;m forgetting that after every run of the outer loop,
the inner loop has to run one less time. So lets keep it at $5n^2 +1$ work. We can also call the amount of work the &amp;rsquo;time complexity&amp;rsquo; of an algorithm.&lt;/p>
&lt;p>Now, what if we slightly change this code, maybe optimize it a bit, then the amount of work would change. But the core of the bubble sort still
remains the same, we still loop over the entire array, for each item in the array. Or what if we implement it in a different language? Having to know
all details of what the compiler translates it into in bytecode would be a hassle. So lets simplify how we calculate the &amp;lsquo;work&amp;rsquo; of the algorithm.&lt;/p>
&lt;h2 id="in-comes-big-o">In comes Big O&lt;/h2>
&lt;p>Big O provides us with a notation, which gives great insight in the efficiency of an algorithm, and how well it scales, without dealing with
the details. Lets start with the calculated work of bubble sort in our previous example. ($5n^2 +1$). If we have an array of $n=100000$, then does the
$+1$ really matter? At that point it has a negligible impact. And big number is what Big O is about. Because if we have an array with 7 elements, then
it does not matter how efficient an algorithm sorts, because it will be done near instant regardless.&lt;/p>
&lt;p>So we are left with $5n^2$. But, as mentioned previously what if we work in another language, or implement it slightly different, then do we need
to run the calculation again. And we want an indicator of how efficient our algorithm is, without it being dependent on language. So in Big O, we can
drop leading constants. Meaning the Big O of our bubble sort algorithm is $O(n^2)$.&lt;/p>
&lt;h2 id="why-big-o">Why Big O&lt;/h2>
&lt;p>While Big O does remove a lot of details, it does so for a good reason. Big O tells us how well an algorithm scales. An algorithm
with Big O of $O(n)$ scales a lot better than one of $O(n^2)$. Doubling the size of the input in the first case, will double the time it takes,
while in the second case, the time quadruples.&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img alt="scale" srcset="
/post/what-is-big-o/graph_hu_cd066c37ce1b56fa.webp 400w,
/post/what-is-big-o/graph_hu_3375385de8a9fb62.webp 760w,
/post/what-is-big-o/graph_hu_6ff95fe7bbb77c25.webp 1200w"
src="https://backendtea.com/post/what-is-big-o/graph_hu_cd066c37ce1b56fa.webp"
width="396"
height="266"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;h2 id="when-not-to-care-about-big-o">When not to care about Big O&lt;/h2>
&lt;p>There are a lot of good reasons to care about the Big O of the algorithms you use. However if you need to sort an array of 50 items,
then no matter what algorithm you use, it will be fast. Big O only starts to matter on bigger input sizes. If you need to deal
with a few thousand items, then there can be a noticeable difference between $O(n \cdot log(n))$ and $O(n^2)$.&lt;/p>
&lt;p>Generally speaking, the lower the Big O, the better, but don&amp;rsquo;t let that be the only fact on which to base your decision.
As stated before, sometimes two algorithms with the same Big O can have a big difference in the run time. So be sure to do some profiling
and see which works best for your use case.&lt;/p>
&lt;h2 id="closing-thoughts">Closing thoughts&lt;/h2>
&lt;p>I left out a few steps on Big O, to keep this a bit simpler. For example, lower order bounds are also ignored in the Big O, and i didn&amp;rsquo;t
go into detail on the mathematical definition of Big O, or how to prove its calculation. But i hope it did give you some insights in
what Big O is, and how you can use it to decide on what algorithm to use.&lt;/p></description></item><item><title>Running Homestead from a separate directory</title><link>https://backendtea.com/post/homestead-separate-directory/</link><pubDate>Wed, 06 Feb 2019 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/homestead-separate-directory/</guid><description>&lt;p>When creating a PHP website, there is a good chance that you wish to use a virtual machine. This has many benefits, like
managing multiple versions of php, or extensions etc.&lt;/p>
&lt;p>A lot of projects use
in combination with
.
But nobody likes to set up a &lt;code>Vagrantfile&lt;/code> every single time. Laravel created
, which
deals with all the vagrant configuration for you. You can install it in your project with &lt;code>composer install laravel/homestead&lt;/code>, and you got it.&lt;/p>
&lt;p>But here comes the problem. If you have a linux VM, and have a windows &amp;lsquo;host&amp;rsquo; system, then you will have to do the entire composer install
twice. Once on the &amp;lsquo;host&amp;rsquo; system, and once again inside the box. Plus if you use older versions of symfony, it could conflict with homestead.&lt;/p>
&lt;p>Instead, we can install it in another directory.&lt;/p>
&lt;p>So inside the project, create a new folder (We&amp;rsquo;ll use &lt;code>homestead&lt;/code>), and cd into it &lt;code>cd homestead&lt;/code>.
There run a &lt;code>composer init&lt;/code>, and specify that you want to use &lt;code>laravel/homestead&lt;/code> as a dependency when asked.
The rest of the file doesn&amp;rsquo;t really matter. Now run a composer install, inside this directory.&lt;/p>
&lt;p>After that you can go back to the root folder &lt;code>cd ..&lt;/code>, and then run the following composer command:
&lt;code>composer install --working-dir homestead&lt;/code>. After that you can run &lt;code>./homestead/vendor/bin/homestead make&lt;/code>.
This will add a few files to the root of your project.&lt;/p>
&lt;p>One thing we will need to update now is the &lt;code>Vagrantfile&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ruby" data-lang="ruby">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># -*- mode: ruby -*-&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># vi: set ft=ruby :&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;json&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="s1">&amp;#39;yaml&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="no">VAGRANTFILE_API_VERSION&lt;/span> &lt;span class="o">||=&lt;/span> &lt;span class="s2">&amp;#34;2&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">confDir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="vg">$confDir&lt;/span> &lt;span class="o">||=&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">expand_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;vendor/laravel/homestead&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dirname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">__FILE__&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">homesteadYamlPath&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">expand_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Homestead.yaml&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dirname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">__FILE__&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">homesteadJsonPath&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">expand_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Homestead.json&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dirname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">__FILE__&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">afterScriptPath&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;after.sh&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">customizationScriptPath&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;user-customizations.sh&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">aliasesPath&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;aliases&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">require&lt;/span> &lt;span class="no">File&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">expand_path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">confDir&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="s1">&amp;#39;/scripts/homestead.rb&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># rest of the file ...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>Lets update one key line:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- confDir = $confDir ||= File.expand_path(&amp;#34;vendor/laravel/homestead&amp;#34;, File.dirname(__FILE__))
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ confDir = $confDir ||= File.expand_path(&amp;#34;homestead/vendor/laravel/homestead&amp;#34;, File.dirname(__FILE__))
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you did everything correctly, your project structure should look something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">\homestead
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> composer.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> composer.lock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> vendor/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">after.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">aliases
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">composer.json
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">composer.lock
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Homestead.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Vagrantfile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">vendor/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you updated this from an old repository, be sure to remove the old &lt;code>laravel/homestead&lt;/code> dependency from the &lt;code>composer.json&lt;/code>.
And update the documentation to run &lt;code>composer install --working-dir homestead&lt;/code> to get the homestead dependency, instead of the normal &lt;code>composer install&lt;/code>.&lt;/p>
&lt;p>Take a look at
to see it in action, and for more in depth homestead configuration, see
&lt;/p>
&lt;small>
Versions used:
&lt;ul>
&lt;li>Composer: 1.8.3&lt;/li>
&lt;li>Homestead: 8.0.2
&lt;/small>&lt;/li>
&lt;/ul></description></item><item><title>Math Is Fun</title><link>https://backendtea.com/post/math-is-fun/</link><pubDate>Wed, 06 Feb 2019 00:57:15 +0100</pubDate><guid>https://backendtea.com/post/math-is-fun/</guid><description>&lt;p>$\sqrt{x} = \frac{x}{\sqrt{x}}$&lt;/p>
&lt;p>Seeing the above equation may look completely logical, or not. When i saw it a few days ago, i thought it was wrong.
When i understood that it was correct, i thought it was the most beautiful thing ever. I&amp;rsquo;m not 100% sure why this
intrigued me so much, but it just looks great.&lt;/p>
&lt;p>There are a few ways of simplifying this. We could multiply both sides with $\sqrt{x}$, like so:&lt;/p>
&lt;p>$\sqrt{x} \cdot \sqrt{x} = \frac{x}{\sqrt{x}} \cdot \sqrt{x}$&lt;/p>
&lt;p>$ x = \frac{x}{\sqrt{x}} \cdot \sqrt{x}$&lt;/p>
&lt;p>$ x = x $&lt;/p>
&lt;p>Another way would be to do cross-multiplication&lt;/p>
&lt;p>$\frac{\sqrt{x}}{1} = \frac{x}{\sqrt{x}}$&lt;/p>
&lt;p>$\sqrt{x} \cdot \sqrt{x} = {x} \cdot 1 $&lt;/p>
&lt;p>$ x = x $&lt;/p>
&lt;p>And there are probably a ton of other ways to effectively &amp;lsquo;solve&amp;rsquo; this. But if there is one thing that i learned
from this simple calculation, is that i actually like doing this. I&amp;rsquo;m having fun with math again!&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div></description></item><item><title>Semantic versioning</title><link>https://backendtea.com/post/semver-dependencies/</link><pubDate>Fri, 09 Nov 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/semver-dependencies/</guid><description>&lt;p>Chances are, if you are writing software, you have some dependencies on other peoples code.
There is no reason to reinvent the wheel, so you use the code someone else wrote.
One of your options is to copy paste it, but then you won&amp;rsquo;t get any updates if they release
a new version.&lt;/p>
&lt;p>This is where dependency managers come into play. With a set of instructions, they retrieve the
needed dependencies for you, and allow you to lock into specific versions, and update when you want.
Before we head into dependency managers, lets first talk about semantic versioning (semver).&lt;/p>
&lt;h2 id="semver">Semver&lt;/h2>
&lt;p>There is a &lt;a href="https://semver.org/" target="_blank" rel="noopener">website&lt;/a> dedicated to explaining what semver entails. For now the most important
parts are the following:&lt;/p>
&lt;ol>
&lt;li>Version numbers look as follows: MAJOR.MINOR.PATCH&lt;/li>
&lt;li>Any &amp;lsquo;breaking&amp;rsquo; change is a Major version up&lt;/li>
&lt;li>Any (set of) new feature(s) is a Minor version up&lt;/li>
&lt;li>Bug fixes and fixes with no impact (refactoring/fixing typos) are a Patch version up&lt;/li>
&lt;/ol>
&lt;p>This is only for project that have a public API, and is only relevant to the public API. Which means
that if you expose a class, for other developers to use, then you can change the private parts of it
without a major version bump. But removing a public method, that the users may call is a breaking change,
which requires a major version up.&lt;/p>
&lt;h2 id="dependency-management">Dependency management&lt;/h2>
&lt;p>Lets take &lt;a href="https://symfony.com/" target="_blank" rel="noopener">symfony&lt;/a> as an example. Let say we have a project, and need the features from
&lt;code>symfony/console&lt;/code> 3.4. In our &lt;code>composer.json&lt;/code> we set the required version like so: &lt;code>&amp;quot;symfony/console&amp;quot;: &amp;quot;^3.4&amp;quot;&lt;/code>.
What we tell composer is the following: We need version 3.4, or higher. But not past a major.
This means that it will attempt to install the highest 3.* version possible, but at least 3.4.0. So if version
4.0.0 will be released, it will not be installed, because that can contain breaking changes.&lt;/p>
&lt;h2 id="before-100">Before 1.0.0&lt;/h2>
&lt;p>Semver works a bit different before a version 1.0.0 is released. Before this version, every MINOR version up is
allowed to have breaking changes, as they are considered pre-releases. This means that if we take the same example as above
but like so: &lt;code>&amp;quot;symfony/console&amp;quot;: &amp;quot;^0.4&amp;quot;&lt;/code>, then we can only install versions of 0.4.*. Meaning that version 0.5.0 will not be
installed, as it can contain breaking changes.&lt;/p>
&lt;p>This is something that is easy to forget, and may cause you to wonder why you aren&amp;rsquo;t updating to the newest version of a package.
So for pre version 1.0.0 packages, be sure to check their updates. There may still be breaking changes between releases, so be sure
to keep up to date with their changes.&lt;/p></description></item><item><title>Add an editorconfig to your project</title><link>https://backendtea.com/post/add-an-editorconfig/</link><pubDate>Fri, 17 Aug 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/add-an-editorconfig/</guid><description>&lt;p>One of the hardest things to spot during a pr are whitespace issues. Did someone use tabs instead of spaces, trailing whitespace etc.
So, why not make it easy on yourself and help any contributors by adding an &lt;code>.editorconfig&lt;/code> file that automatcially fixes those things for you.&lt;/p>
&lt;p>An editorconfig is an ini like file that helps your IDE or text editor to keep certain standards. Even this blog, which is written in mostly markdown
has the following editorconfig:&lt;/p>
&lt;p>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="na">root&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">[*]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">charset&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">utf-8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">end_of_line&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">lf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">trim_trailing_whitespace&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">insert_final_newline&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">indent_style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">space&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">indent_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">4&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;code>root=true&lt;/code> means that it is the top level editorconfig. This is usefull if you have certian directories that have a different style for whatever reason.
The charset is set to utf-8, we use &amp;rsquo;normal&amp;rsquo; line endings, and trim all trailing whitespace.
All files have an empty new line at the end, and are indented with 4 spaces.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>If i have a &lt;code>Makefile&lt;/code>, which needs to be indented with tabs i can add the following:&lt;/p>
&lt;p>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="k">[Makefile]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">indent_style&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">tab&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
And now my makefile is correctly indented with tabs.&lt;/p>
&lt;p>If the project contains &lt;code>js&lt;/code> and &lt;code>scss&lt;/code> files that i want indented with 2 spaces i can add the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="k">[*.{js,scss}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">indent_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">2&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The &lt;a href="https://editorconfig.org/" target="_blank" rel="noopener">editor config website&lt;/a> has an explanation on what options there are, and how to specify what files to affect etc.
Some editors check for a &lt;code>.editorconfig&lt;/code> file by default, while other need a plugin to understand and use them. You can find the full list on their
website as well. (But PHPstorm needs a plugin, so please do install it.)&lt;/p></description></item><item><title>What is a default object?</title><link>https://backendtea.com/post/default-objects/</link><pubDate>Fri, 10 Aug 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/default-objects/</guid><description>&lt;p>What is a clean solution to display a &amp;lsquo;default&amp;rsquo; message to the user, when something they try to access isn&amp;rsquo;t there (anymore)?
We could let the our repository throw an exception, catch it somewhere, and then let our controller handle it. Maybe we could
return null, and pass that all the way up and add a fall back message for the content in the view somewhere?
Why not try a better solution and work with a default object.&lt;/p>
&lt;p>A default object (or null object) should come from business requirements. Lets say you are writing a website for bloggers.
If the user goes to see a blog post that doesn&amp;rsquo;t exist(anymore), it should display the error message:
&lt;code>Sorry, no blog post was found on this url&lt;/code>. There are multiple ways to do this, so lets first look at doing this the
&amp;lsquo;old fashioned&amp;rsquo; way and throw an exception somewhere:
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//Blog.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Blog&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">fromDB&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">Entity&lt;/span> &lt;span class="nv">$blogEntity&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">//logic
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Blog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$title&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$content&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getTitle&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">title&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getContent&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">content&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//BlogService.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BlogService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param int $id
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @throws BlogNotFoundException
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @return Blog
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getBlog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Blog&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$blogEntity&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">db&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$blogEntity&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="nx">BlogNotFoundException&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">notFoundById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">Blog&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">fromDB&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$blogEntity&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//BlogController.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BlogController&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">showBlog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$blogId&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$blg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">blogservice&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getBlog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$blogId&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">render&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;blog.show.html&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$blog&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getTitle&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;content&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$blog&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getContent&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$blog&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">blogService&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getBlog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$blogId&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">BlogNotFoundException&lt;/span> &lt;span class="nv">$e&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">render&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;blog.show.html&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;Sorry, no blog post was found on this url&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;content&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>Chances are that you&amp;rsquo;ve seen code like this before. Or maybe the service would return null, and then the logic of displaying the title or the default text was
placed somewhere in the view. This means that multiple places must &amp;lsquo;care&amp;rsquo; about whether or not the blog post exists. It also causes the requirements to be
hidden somewhere within your infrastructure. It may make it harder in case the business decides they want a different fall back message.&lt;/p>
&lt;p>Now lets refactor the code to use a default object, and see what that looks like.&lt;/p>
&lt;p>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//Blog.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Blog&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">fromDB&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$blogEntity&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">//logic
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Blog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$title&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$content&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">fromNotFound&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Blog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Sorry, no blog post was found on this url&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getTitle&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">title&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getContent&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">content&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//BlogService.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BlogService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getBlog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Blog&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$blogEntity&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">db&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$blogEntity&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">Blog&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">fromDB&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$blogEntity&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="nx">Blog&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">fromNotFound&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//BlogController.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BlogController&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">showBlog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$blogId&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">render&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;blog.show.html&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$blog&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getTitle&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;content&amp;#39;&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$blog&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getContent&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
Now to me, this looks a lot cleaner. The Controller no longer knows if the blog was found or not, and when you read the code it is still clear what is
happening. We use a named constructor here, which makes it clear what is going on. We could also opt to use a separate class entirely.
One of the benefits of doing so is that you could place in your &lt;code>Domain&lt;/code> namespace since this deals with domain logic (if you are separating into domain
and infrastructure). I personally prefer to make all classes final or abstract, so i use a named constructor.&lt;/p>
&lt;p>An implementation with a separate class may look something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//NotFoundBlog.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">NotFoundBlog&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">Blog&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getTitle&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;Sorry, no blog post was found on this url&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getContent&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//BlogService.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BlogService&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getBlog&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">Blog&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$blogEntity&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">db&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getById&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$id&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$blogEntity&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="nx">Blog&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">fromDB&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$blogEntity&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">NotFoundBlog&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>If you enjoyed this post, have comments, feedback, or anything else, feel free to comment, or hit me up on &lt;a href="https://twitter.com/backendtea" target="_blank" rel="noopener">twitter&lt;/a>&lt;/p></description></item><item><title>Noop polyfills</title><link>https://backendtea.com/post/noop-polyfill/</link><pubDate>Sun, 05 Aug 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/noop-polyfill/</guid><description>&lt;p>A while ago a &lt;a href="https://www.reddit.com/r/PHP/comments/91ffbi/package_in_v99999_is_this_normal/" target="_blank" rel="noopener">reddit post&lt;/a> showed up, where someone installed version &lt;code>9.99.99&lt;/code> of &lt;a href="https://github.com/paragonie/random_compat" target="_blank" rel="noopener">paragonie/random_compat&lt;/a>.
Seeing a package update from &lt;code>2.*&lt;/code> to &lt;code>9.99.99&lt;/code> may be a bit confusing, but given how autolaoding, polyfills and composer work in php, this is
actually quite a clever way of dealing with things. Lets take a look at version constraints, autoloading and composer to see why.&lt;/p>
&lt;h2 id="applying-a-polyfill">Applying a polyfill&lt;/h2>
&lt;p>The purpose of the polyfill in question is to provide &lt;code>random_*&lt;/code> functions to php versions below 7.0. As these functions were only
introduced in php 7.0. You can&amp;rsquo;t declare these functions yourself in versions over 7.0, as it has already been declared by php itself.
Resulting in an error like: &lt;code>Fatal error: Cannot redeclare random_bytes() in /foo.php on line 6&lt;/code>.&lt;/p>
&lt;p>The file that gets loaded therefore first checks the php version. In other cases, like polyfilling an extension, it would check
the existence of that function, or if the extension is loaded.&lt;/p>
&lt;h2 id="autoloading">Autoloading&lt;/h2>
&lt;p>A composer.json with a section something like this may be familiar.
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;autoload&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;psr-4&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Acme\\Foo\\&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;src/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;files&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;src/Helpers.php&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;src/global-functions.php&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/p>
&lt;p>We use the &lt;a href="https://www.php-fig.org/psr/psr-4/" target="_blank" rel="noopener">psr-4&lt;/a> standard for autoloading, and we load a few files as well that contain global functions/helpers. I won&amp;rsquo;t go into detail
about psr 4 as that isn&amp;rsquo;t relevant for this blog post. But lets look at how the files are autoloaded by composer.&lt;/p>
&lt;p>If we dive into our &lt;code>vendor/composer/autoload_real.php&lt;/code> we will find something like the following. The class names may differ as they are auto-generated, but
it should generally look like this.&lt;/p>
&lt;p>The &lt;code>$includeFiles&lt;/code> will be all the files that are in the &lt;code>autoload.files&lt;/code> part of composer.json (as well as the composer.json files from packages you depend on).
We can see that if the file hasn&amp;rsquo;t been required already, its required. This is done as soon as you include/require &lt;code>vendor/autoload.php&lt;/code>, which is generally
done at the start of every process.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ComposerAutoloaderInit0e3a5124969d920e9ee3b981a725692a&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">//...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getLoader&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">//...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$useStaticLoader&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$includeFiles&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Composer\Autoload\ComposerStaticInit0e3a5124969d920e9ee3b981a725692a&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="nv">$files&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$includeFiles&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">require&lt;/span> &lt;span class="no">__DIR__&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s1">&amp;#39;/autoload_files.php&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$includeFiles&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nv">$fileIdentifier&lt;/span> &lt;span class="o">=&amp;gt;&lt;/span> &lt;span class="nv">$file&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">composerRequire0e3a5124969d920e9ee3b981a725692a&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$fileIdentifier&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$file&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$loader&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">composerRequire0e3a5124969d920e9ee3b981a725692a&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$fileIdentifier&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$file&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">empty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$GLOBALS&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;__composer_autoload_files&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="nv">$fileIdentifier&lt;/span>&lt;span class="p">]))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">require&lt;/span> &lt;span class="nv">$file&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$GLOBALS&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;__composer_autoload_files&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="nv">$fileIdentifier&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This means that if you have paragonie/random_compat as a dependency, even if its an indirect dependency, you will require its bootstrap file every time.
Which means that ever process starts with a check to see if you have php 7.0 or not.&lt;/p>
&lt;h2 id="noop-versions">Noop versions&lt;/h2>
&lt;p>This is where the noop version comes in. The version that does &amp;rsquo;nothing&amp;rsquo;. It doesn&amp;rsquo;t populate the autoload, thus the files are never required. It has a
really high version, so that you can require &lt;code>~1.0||~2.0||~9.99.99&lt;/code>. Composer will always attempt to install the highest possible version of a package.
So if you have php 7.0 or higher, it will install version &lt;code>9.99.99&lt;/code>, and if you don&amp;rsquo;t it will attempt to install a lower version. This is currently the
best way a package can declare to &amp;rsquo;not&amp;rsquo; be installed if certain criteria are met.&lt;/p>
&lt;p>So if you have a package that uses paragonie/random_compat, and you are installable on php 7.0+, update the requirements to &lt;code>9.99.99&lt;/code>. If you are not
compatible with php below 7.0, and you get paragonie/random_compat from another dependency which doesn&amp;rsquo;t allow &lt;code>9.99.99&lt;/code> to install, consider adding the
following to your composer. This will completely remove the package from your dependency tree. You shouldn&amp;rsquo;t do this if you are compatible with lower php versions.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;replace&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;paragonie/random_compat&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;*&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;p>If you are directly using a symfony polyfill, you may soon also be able to require a version &lt;code>9.*&lt;/code> that does the same thing, &lt;a href="https://github.com/symfony/polyfill/pull/138" target="_blank" rel="noopener">as it may soon become available&lt;/a>.&lt;/p>
&lt;p>If you wish to know more about polyfills and extensions, check out my &lt;a href="https://backendtea.com/post/extension-polyfill/">other blog post about polyfills and exentions&lt;/a>.&lt;/p></description></item><item><title>Deploying to Github pages with Travis</title><link>https://backendtea.com/post/deploy-travis-to-github/</link><pubDate>Tue, 31 Jul 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/deploy-travis-to-github/</guid><description>&lt;p>If you want to write a blog, or have another kind of static website, you need to deploy it somewhere.
So why not host it on Github pages? It&amp;rsquo;s free and has https by default.&lt;/p>
&lt;p>To deploy a website that gets generated, for example by &lt;a href="https://jekyllrb.com/" target="_blank" rel="noopener">jekyll&lt;/a>, we need to make a few steps before
we can deploy it to Github pages.&lt;/p>
&lt;p>We need the following things:&lt;/p>
&lt;ul>
&lt;li>Two Github repositories&lt;/li>
&lt;li>Set up Travis&lt;/li>
&lt;/ul>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="two-github-repositories">Two Github repositories&lt;/h2>
&lt;p>Because we want the code that &amp;lsquo;creates&amp;rsquo; the website, and the website itself on Github, we need two repositories. The first one can be
whatever you wish to call it. In this example the repository will simply be called Blog. The second repository, which holds the website needs a special name.
This repository will be called &lt;code>&amp;lt;Your github user name&amp;gt;.github.io&lt;/code>. So in my case it is called &lt;code>BackEndTea.github.io&lt;/code>.&lt;/p>
&lt;p>Once you have added some content to the Blog repository, and want to show it to the world, follow the next steps.&lt;/p>
&lt;h2 id="set-up-travis">Set up Travis&lt;/h2>
&lt;p>Thankfully, creating a Travis account is as easy as going to their &lt;a href="https://travis-ci.org/" target="_blank" rel="noopener">website&lt;/a>, and clicking the Sign in with Github button.
There you will find a list of all (public) repositories you have access to. Activate the one for Blog like so:&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img src="https://backendtea.com/img/blog-settings.png" alt="Blog settings" loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>Now lets add a file called &lt;code>.travis.yml&lt;/code> to the root of our Blog project, which will look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">language&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ruby&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">cache&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bundler&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">branches&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">only&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">master&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">install&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">bundler install&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">bundler exec jekyll build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">deploy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">pages&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">skip_cleanup&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">local_dir&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">_site&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">github_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">$GITHUB_TOKEN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Set in travis-ci.org dashboard&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">keep-history&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">branch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">master&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">repo&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">&amp;lt;Your-github-name&amp;gt;/&amp;lt;Your-github-name&amp;gt;.github.io&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">target_branch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">master&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Replace the language/install/script parts with what is relevant for your tool. Since this example uses Jekyll we need ruby, install the project with bundler,
and run &lt;code>bundler exec jekyll build &lt;/code> to build the project, which puts it in the &lt;code>_site&lt;/code> folder by default.&lt;/p>
&lt;p>The deploy part is a bit more interesting. The provider key is pages, as we are deploying to Github pages. We set &lt;code>skip_cleanup&lt;/code> to true,
as this would otherwise &amp;lsquo;clean up&amp;rsquo; the freshly build website. &lt;code>local_dir&lt;/code> is the folder to actually deploy. If we set &lt;code>keep-history&lt;/code> to false,
every push will be force pushed, and the repository will have no git history. The &lt;code>repo&lt;/code> is the Github repository to deploy to, which we created in the first step. And we deploy to master.&lt;/p>
&lt;p>Now for the &lt;code>github_token&lt;/code>. We go back to Github and go to our settings like so:&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img src="https://backendtea.com/img/github-settings.png" alt="github-settings" loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>There you can go to Developer settings, and then to Personal access tokens. Click the Generate new token button, and give it the following permissions:&lt;/p>
&lt;p>
&lt;figure >
&lt;div class="flex justify-center ">
&lt;div class="w-100" >&lt;img src="https://backendtea.com/img/travis-deployment.png" alt="travis-permissions" loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;/figure>
&lt;/p>
&lt;p>This should be the only permissions it needs.&lt;/p>
&lt;p>Once you the token it will show you the token &lt;em>one time only&lt;/em>, so don&amp;rsquo;t close the tab yet. Move back to Travis and go to the settings of your repository.
There you will see a section called &lt;code>Environment Variables&lt;/code>. On the left is the key to access the value, and the right is the value itself.&lt;/p>
&lt;p>The key here will be &lt;code>GITHUB_TOKEN&lt;/code>, which is the same as we put in our &lt;code>.travis.yml&lt;/code>, without the leading &lt;code>$&lt;/code>. The value is the token we just generated.
Once you pasted it to Travis you will lose the ability to see the token. Thankfully, we no longer need the token ourselves, as from now on Travis will deal
with the deployments for us.&lt;/p>
&lt;p>With our current settings, every time we push to master, our website will be re-build and deployed to Github pages automatically.
As a note, don&amp;rsquo;t re-use your github tokens, and only give it the permissions it needs, and if you no longer use it, delete it.
If the token gets compromised it could be used to make changes to your repositories and do a lot of damage.&lt;/p></description></item><item><title>Infection 0.9 is out!</title><link>https://backendtea.com/post/infection-09-release/</link><pubDate>Wed, 25 Jul 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/infection-09-release/</guid><description>&lt;p>&lt;a href="https://backendtea.com/post/test-your-tests-are-testing/">I wrote about infection a while ago&lt;/a>, and not too long after that, &lt;a href="https://github.com/infection/infection/releases/tag/0.9.0" target="_blank" rel="noopener">0.9.0 was released&lt;/a>.
Lets look at the new features, and how we can use them in our projects!&lt;/p>
&lt;h2 id="profiles">Profiles&lt;/h2>
&lt;p>Previously, if you wanted to run all mutators, except for one, it would involve passing all mutators in the commandline,
except for the one you didn&amp;rsquo;t want, which was tedious. With the addition of profiles this became a whole lot easier.&lt;/p>
&lt;p>Here we have a simple configuration file, where we use all mutators, except for the &lt;code>@function_signature&lt;/code> ones (&lt;code>PublicVisiblity&lt;/code> &amp;amp; &lt;code>ProtectedVisibility&lt;/code>).&lt;/p>
&lt;p>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;timeout&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;source&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;directories&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;src&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;logs&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;infection-log.txt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;mutators&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;@default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;@function_signature&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
Instead of a profile (which always starts with an &lt;code>@&lt;/code> and is snake_case), you can also disable a single mutator directly.
e.g. &lt;code>&amp;quot;PublicVisiblity&amp;quot;: false&lt;/code>.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="disabling-mutators-for-specific-places">Disabling mutators for specific places.&lt;/h2>
&lt;p>Besides passing true or false to a mutator in the config, we now have the ability to add specific settings to it.&lt;/p>
&lt;p>Lets say we want to disable the &lt;code>Plus&lt;/code> mutator for the method named &lt;code>doPlus&lt;/code> in the &lt;code>Foo\Bar\Baz&lt;/code> class, we can do it like so:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;mutators&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;@default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Plus&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ignore&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Foo\\Bar\\Baz::doPlus&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>But what if we want to disable the &lt;code>Minus&lt;/code> mutator, for all classes in the &lt;code>Foo\Bar&lt;/code> namespace, and the
&lt;code>@function_signature&lt;/code> profile for all methods named &lt;code>noTrue&lt;/code> in the &lt;code>Foo&lt;/code> namespace?&lt;/p>
&lt;p>It can easily be done by adding the following parts to your configuration file.
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;mutators&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;@default&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Minus&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ignore&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Foo\\Bar\\**&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;@function_signature&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ignore&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Foo\\**::noTrue&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/p>
&lt;p>&lt;strong>Important:&lt;/strong> If you want to (partially) disable certain mutators or profiles, but still run everything else, be sure to add
&lt;code>&amp;quot;@default&amp;quot;: true&amp;quot;&lt;/code> to your config, which enables all the other mutators.&lt;/p>
&lt;h2 id="mutation-badge">Mutation Badge&lt;/h2>
&lt;p>&lt;img style="float:left" src="https://badge.stryker-mutator.io/github.com/infection/infection/master"> &lt;br>&lt;/p>
&lt;p>Thanks to the people who made &lt;a href="https://stryker-mutator.io/" target="_blank" rel="noopener">Stryker&lt;/a>, you can now add a new badge to show off your mutation score.
You can find the instructions right &lt;a href="https://infection.github.io/guide/mutation-badge.html" target="_blank" rel="noopener">here&lt;/a>.&lt;/p>
&lt;h2 id="smarter-mutators">Smarter mutators&lt;/h2>
&lt;p>This mutation would always result in the following error, making it a useless mutation.&lt;/p>
&lt;p>&lt;code>Fatal error: Access level to B::foo() must be public (as in class A) in /foo/test.php on line 14&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">abstract class A
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> abstract public function foo();
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">class B extends A
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- public function foo()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ protected function foo()
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Infection now uses reflection to determine if a method has a parent class or an interface that declares the same method.
And it does not mutate the visibility if the method is inherited.
If you use a lot of abstract/parent classes and interfaces this could drastically reduce the amount of mutations you get,
and thus speed up the process by a lot.&lt;/p>
&lt;h2 id="phar-scoping">Phar Scoping&lt;/h2>
&lt;p>Now you may be worried, that autoloading your code, when using a phar could result in a clash while autoloading.
e.g. your project has a dependency on &lt;code>Symfony/Console&lt;/code>, and the phar does as well, so which one does infection load?
This has been fixed by using &lt;a href="https://github.com/humbug/php-scoper" target="_blank" rel="noopener">PHP-Scoper&lt;/a>, which prefixes all namespaces within the phar with a random string, and
makes it so that the code, and autoloading, within the phar can not clash with the code in your project.&lt;/p>
&lt;p>For example, your project would use (and thus autoload) the following class &lt;code>Symfony\Component\Console\Application&lt;/code>.
Now infection also uses this class, but within the phar its called something like: &lt;code>ScopedRandomgString\Symfony\Component\Console\Application&lt;/code>.
This means that infection has a separate version of the class that it uses.&lt;/p>
&lt;p>&lt;a href="https://github.com/humbug/php-scoper" target="_blank" rel="noopener">PHP-Scoper&lt;/a> a really cool project, so if you are interested in it, be sure to check it out.&lt;/p>
&lt;h2 id="new-mutators">New mutators&lt;/h2>
&lt;p>Not only did mutators get smarter, we also got a whole lot of new mutators:&lt;/p>
&lt;p>&lt;code>Finally_&lt;/code>:&lt;/p>
&lt;p>Removes the &lt;code>finaly&lt;/code> block that is part of a try catch block, e.g.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">try {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> dangerous_function()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">catch(Exception $e) {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> //handle exception
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- finally {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- // Do this no matter what
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- }
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This should help you test your error handling.&lt;/p>
&lt;p>&lt;code>PregQuote&lt;/code>:&lt;/p>
&lt;p>Removes &lt;code>preg_quote&lt;/code> function calls, e.g.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- $a = preg_quote(&amp;#39;value&amp;#39;);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ $a = &amp;#39;value&amp;#39;;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This should help you test the usage of &lt;code>preg_quote&lt;/code> calls, and maybe even show you don&amp;rsquo;t need/use them.&lt;/p>
&lt;p>&lt;code>ArrayItem&lt;/code>&amp;amp; &lt;code>Yield_&lt;/code>:&lt;/p>
&lt;p>Changes the &lt;code>=&amp;gt;&lt;/code> into &lt;code>&amp;gt;&lt;/code> for arrays and &lt;code>yield&lt;/code> respectively,&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">[
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- $a-&amp;gt;var =&amp;gt; $b-&amp;gt;var
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ $a-&amp;gt;var &amp;gt; $b-&amp;gt;var
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span>]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This should help to test the usages of your arrays and yield statements&lt;/p>
&lt;p>&lt;code>Assignment&lt;/code>:&lt;/p>
&lt;p>Changes assignments like &lt;code>+=&lt;/code>. &lt;code>-+&lt;/code>, &lt;code>.=&lt;/code> etc into plain &lt;code>=&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">$a = &amp;#39;title;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- $a .= &amp;#39; suffix&amp;#39;;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ $a = &amp;#39; suffix&amp;#39;;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This should help you test that assignments are done properly.&lt;/p>
&lt;p>&lt;code>For_&lt;/code>:&lt;/p>
&lt;p>The &lt;code>for&lt;/code> counterpart to the &lt;code>ForEach_&lt;/code> mutator that stops for loops from happening:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- for($i =0; $i &amp;lt; count($array); $i++)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ for($i =0; false; $i++)
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This should help testing that your for loops are actually happening.&lt;/p>
&lt;h2 id="updating">Updating&lt;/h2>
&lt;p>If you use composer to add infection as a dev dependency, be sure to change
the version restriction in your composer.json to &lt;code>^0.9.0&lt;/code>, and hit &lt;code>composer update&lt;/code>. Because in pre &lt;code>1.0.0&lt;/code> releases
composer won&amp;rsquo;t bump minor versions, as they could have breaking changes.&lt;/p>
&lt;p>If you are downloading the par, simply change the current version in the url to &lt;code>0.9.0&lt;/code> and you should be good.&lt;/p>
&lt;h2 id="in-conclusion">In conclusion&lt;/h2>
&lt;p>Infection 0.9 came with a lot of improvements, especially for (partially) disabling mutators or groups of mutators.
The mutators also got a bit smarter, and hopefully you should see your mutation testing being done a lot quicker.&lt;/p>
&lt;p>If you liked this blog post, and/or have questions/tips/whatever, feel free to leave a comment here, or hit me up on
&lt;a href="https://twitter.com/BackEndTea" target="_blank" rel="noopener">twitter&lt;/a>&lt;/p></description></item><item><title>PHP will lose its LTS</title><link>https://backendtea.com/post/php-loses-lts/</link><pubDate>Tue, 24 Jul 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/php-loses-lts/</guid><description>&lt;p>In about &lt;a href="https://secure.php.net/supported-versions.php" target="_blank" rel="noopener">5 months&lt;/a> PHP 5.6 loses its security support. Which means that in 5 months, PHP loses its current Long Term Support(LTS) version.
If you don&amp;rsquo;t count security only support as LTS, then PHP hasn&amp;rsquo;t had an LTS for the past 1.5 years. If your website can&amp;rsquo;t handle PHP 7 or higher,
you may start finding it harder to get support for that, with hosting sites like acquia &lt;a href="https://support.acquia.com/hc/en-us/articles/360005518633-PHP-5-6-Retirement-FAQ" target="_blank" rel="noopener">removing php 5.6 support&lt;/a>
We will look at the advantages, and disadvantages of LTS, and what it means for the PHP ecosystem.&lt;/p>
&lt;h2 id="what-is-lts">What is LTS&lt;/h2>
&lt;p>In essence, LTS is exactly what it says it is. A specific version gains longer that usual support. Take Symfony for example. Every release has 8 months of
active, bug fixing, support. After that it has 6 months of security only support, and after that the version has no official support.
The LTS versions (3.4, and 4.4 etc. in the future) have 3 years of bug support, and then 1 more year of security only support.
Generally, there will also be a clear upgrade path between the latest LTS and the current one.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="why-lts">Why LTS&lt;/h2>
&lt;p>Now you may wonder, why would i support a specific version for a long amount of time, when there are newer versions with more features. The reason for that is
simple. If a company uses your framework/package/programming language a lot, every backwards incompatible change could mean a lot of rework to their applications.
But not updating means bugs wont be fixed and security vulnerabilities wont be patched. LTS solves both problems by keeping everything the same, and only fixing bugs
and/or security issues.&lt;/p>
&lt;p>Of course LTS also has downsides. For the maintainers it means that every bug fix or security patch has to be applied to the LTS version first, and then added on top of the
current version. And then there is still the fixing of any conflicts that could be caused by the bug and the difference between LTS and the current version.&lt;/p>
&lt;h2 id="php-lts">PHP LTS&lt;/h2>
&lt;p>Now PHP&amp;rsquo;s LTS is a bit different. Every minor version already has 2 years of bug fix support, and then 1 year of security only support. Which means you &lt;em>could&lt;/em> already
consider them LTS. Another difference is that PHP does not follow &lt;a href="https://semver.org/" target="_blank" rel="noopener">semantic versioning&lt;/a>. Which in essence means that almost every minor update(e.g. 7.1 -&amp;gt;7.2)
has one or more backwards incompatible changes. It may also require one to update dependencies that are incompatible with those changes, which could
end up being a lot of work, as it may require updating major versions of those dependencies.&lt;/p>
&lt;p>If you asked someone what version your new PHP package should support, a lot of people would have told you to support 5.6. With its LTS ending, soon, 7.0 seems to be all
the new go to lowest version. The funny thing is that 7.0 will lose its security support in 4 months, which is earlier than 5.6. So some opt to make the lowest version 7.1
or 7.2.&lt;/p>
&lt;p>Until PHP gains a new LTS, any &lt;strong>new&lt;/strong> package should support the last, or last 2, versions of PHP. Unless you are filling a very specific niche, or need to
stay in line with other packages, supporting 5.6 shouldn&amp;rsquo;t really be done any more. And please don&amp;rsquo;t create new packages for PHP 5.3. Now if you are already
maintaining something, upgrading the minimum PHP versions is a backwards incompatible change, and means you have to bump the major version if you follow
semantic versioning. If a new PHP LTS gets released we should attempt to support that version as a minimum until it gets deprecated.&lt;/p></description></item><item><title>Automated code reviews</title><link>https://backendtea.com/post/auto-review-testing/</link><pubDate>Thu, 28 Jun 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/auto-review-testing/</guid><description>&lt;p>Sometimes a code base has specific code style rules, that aren&amp;rsquo;t easy to detect with either with a style fixer or a static analyzer.
So instead of having to tell every new contributor your rules during a PR, or lay them all out in your CONTRIBUTING.md,
why not automatically check them with PHPUnit.
We will look at a couple of different &amp;lsquo;rules&amp;rsquo; your code base could have, and how to set up the automated tests.&lt;/p>
&lt;p>The goal of these tests is not to replace code reviews. Instead it allows your code review to focus on the actual code, rather than
having to read through a PR to check if classes are final, or if they don&amp;rsquo;t expose public properties.
It allows you to focus on those important things, like what the code is doing, and how it is doing it.
It won&amp;rsquo;t replace static analyzers either, as the goal of these tests is to automate those little, usually opinionated things, that are
hard to put into a single &amp;lsquo;rule&amp;rsquo; for static analysis, but instead differ greatly from project to project.&lt;/p>
&lt;p>We will go through writing tests for the following cases:&lt;/p>
&lt;ul>
&lt;li>Source classes must have no public properties&lt;/li>
&lt;li>Source classes must be final&lt;/li>
&lt;li>Source classes must have a corresponding unit test&lt;/li>
&lt;/ul>
&lt;p>Hopefully this should get your familiar with how to write these tests.&lt;/p>
&lt;p>But before we do that, we have to gather a list of all our classes for which we can use&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="getting-the-classes">Getting the classes&lt;/h2>
&lt;p>To get our source classes, we use the &lt;code>symfony/finder&lt;/code> component, and we write two methods, assuming our project uses psr-4.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//ProjectCodeTest.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">providesSourceClasses&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$item&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">$item&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getSrcClasses&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getSrcClasses&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">static&lt;/span> &lt;span class="nv">$classes&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">null&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="nv">$classes&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$classes&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$finder&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Finder&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">create&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">files&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;*.php&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">in&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">__DIR__&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s1">&amp;#39;/../../src&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">notName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;helpers.php&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$classes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">SplFileInfo&lt;/span> &lt;span class="nv">$file&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;%s\\%s%s%s&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;VendorNamespace&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">strtr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$file&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getRelativePath&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">DIRECTORY_SEPARATOR&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;\\&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$file&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getRelativePath&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="s1">&amp;#39;\\&amp;#39;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$file&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getBasename&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;.&amp;#39;&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nv">$file&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getExtension&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">iterator_to_array&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$finder&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sort&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$classes&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$classes&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The &lt;code>providesSourceClasses&lt;/code> is our dataProvider, that we use on all our tests. Lets take a look at the &lt;code>getSrcClasses&lt;/code> method and see what it does.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">static&lt;/span> &lt;span class="nv">$classes&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">null&lt;/span> &lt;span class="o">!==&lt;/span> &lt;span class="nv">$classes&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$classes&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//...
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The variable we will assign the list of classes to is &lt;code>$classes&lt;/code>. By using a static variable, we can cache it within the method, so the next time we don&amp;rsquo;t have to iterate over the file system again, and simply return our cached list.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nv">$finder&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">Finder&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">create&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">files&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">name&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;*.php&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">in&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">__DIR__&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s1">&amp;#39;/../../src&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">notName&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;helpers.php&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//...
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>We create a finder object that gets us all files ending in &lt;code>.php&lt;/code> in the src directory (your indentation level may be different), except for &lt;code>helpers.php&lt;/code>.
In this scenario we exclude the &lt;code>helpers.php&lt;/code> file as it is a file with functions, and not a class.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//...
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="nv">$classes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">SplFileInfo&lt;/span> &lt;span class="nv">$file&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;%s\\%s%s%s&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;VendorNamespace&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">strtr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$file&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getRelativePath&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="nx">DIRECTORY_SEPARATOR&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;\\&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$file&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getRelativePath&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="s1">&amp;#39;\\&amp;#39;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$file&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getBasename&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;.&amp;#39;&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nv">$file&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getExtension&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">iterator_to_array&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$finder&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">false&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">sort&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$classes&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="nv">$classes&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>We use &lt;a href="https://secure.php.net/manual/en/function.array-map.php" target="_blank" rel="noopener">array_map&lt;/a> to loop over all the files found by our finder, which are &lt;code>SplFileInfo&lt;/code> objects,
and replace change those into a Fully Qualified Name(FQN) strings of our classes. The &lt;code>VendorNamespace&lt;/code> is the namespace you use before everything else. If the autoload in composer.json looks like this, you would replace it with &lt;code>Acme\\Foo&lt;/code>.
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;autoload&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;psr-4&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;Acme\\Foo&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;src/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
We then append the relative path, starting in the src folder, and replace our directory separators with a &lt;code>\&lt;/code>, to turn it into its namespace.
If the relative path is empty, its directly in the src folder, we do not add another &lt;code>\&lt;/code> since its already after the vendor namespace, otherwise,
we add a new &lt;code>\&lt;/code>, and then finally we append the name of the file, without its suffix. Which should turn into our class name.&lt;/p>
&lt;p>The &lt;a href="https://secure.php.net/manual/en/function.iterator-to-array.php" target="_blank" rel="noopener">iterator_to_array&lt;/a> is used as &lt;code>array_map&lt;/code> doesn&amp;rsquo;t accept generators.
We then sort it and return it to our provider.&lt;/p>
&lt;p>Our provider wraps it into an array to make sure it can be used by our PHPUnit tests.&lt;/p>
&lt;p>Now, the final step before we can finally write those tests to check our code style, is to make sure this provider is valid. We may have another file
that doesn&amp;rsquo;t contain a class that gets picked up by our finder, or someone may add one later and run into strange errors.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @dataProvider providesSourceClasses
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string $className
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">test_src_class_provider_is_valid&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">class_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">interface_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="nx">trait_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;The &amp;#34;%s&amp;#34; class was picked up by the source files finder, but it is not a class, interface or trait. &amp;#39;&lt;/span> &lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Please check for typos in the class name. Or exclude the file if in the ProjectCodeTest if it is not a class.&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This simply checks if the provided class name is either a class, interface or trait. And then gives a &lt;strong>descriptive&lt;/strong> failure message to the user.&lt;/p>
&lt;h2 id="error-messages">Error Messages&lt;/h2>
&lt;p>As seen in the example above, half of the method is the error message. Since we want these tests to help our uses adhere to our standards, it should be
clear why the test is failing. Simply seeing &lt;code>Failed asserting that false is true.&lt;/code> doesn&amp;rsquo;t help our users at all.&lt;/p>
&lt;p>And for what its worth, descriptive test failure messages are always a good idea, as it helps you figure out why a test is suddenly failing.&lt;/p>
&lt;h2 id="the-rules">The rules&lt;/h2>
&lt;p>&lt;strong>Source classes must have no public properties&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @dataProvider providesSourceClasses
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string $className
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">test_src_classes_do_not_expose_public_properties&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$rc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">\ReflectionClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$properties&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$rc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getProperties&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">\ReflectionProperty&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">IS_PUBLIC&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$properties&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">array_filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$properties&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">\ReflectionProperty&lt;/span> &lt;span class="nv">$property&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">use&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$property&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">class&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertCount&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$properties&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Class &amp;#34;%s&amp;#34; should not declare public properties, &amp;#39;&lt;/span> &lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;if it has properties that need to be accessed, consider getters and/or setters instead. &lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">Violations:&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">%s&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">implode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$item&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34; * &lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">item&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span> &lt;span class="nv">$properties&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Lets go through the code again, and see what it does.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$rc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">\ReflectionClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$properties&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$rc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getProperties&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">\ReflectionProperty&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">IS_PUBLIC&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$properties&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">array_filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$properties&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">\ReflectionProperty&lt;/span> &lt;span class="nv">$property&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">use&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$property&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">class&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">});&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>We create a &lt;a href="https://secure.php.net/manual/en/class.reflectionclass.php" target="_blank" rel="noopener">ReflectionClass&lt;/a> of our class, and get all public properties.&lt;/p>
&lt;p>We then filter this down to only the properties of the current class, by removing any properties that belong to parent classes.
This makes sure that we don&amp;rsquo;t get a lot of errors if a parent class has a public property, or if we extend from a class outside our code base that has public properties.&lt;/p>
&lt;p>Of course there might be edge cases, where you want to have public properties.
For example, if you create your own &lt;a href="https://secure.php.net/manual/en/class.streamwrapper.php" target="_blank" rel="noopener">stream wrapper&lt;/a>, you need a public property named &lt;code>context&lt;/code>.
So if you have a stream wrapper named &lt;code>StreamWrapperClass&lt;/code>, you can add the following snippet before the assertion.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="nx">StreamWrapperClass&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// The StreamWrapperClass needs 1 public property: $context
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// @see https://secure.php.net/manual/en/class.streamwrapper.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertCount&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$properties&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;The &amp;#34;%s&amp;#34; class must have exactly 1 public property as it is a streamwrapper. &amp;#39;&lt;/span> &lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;If this has changed due to recent php developments, consider updating this test.&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;context&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$properties&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getName&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;The &amp;#34;%s&amp;#34; class must have exactly 1 public property named context. &amp;#39;&lt;/span> &lt;span class="o">.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;If this has changed due to recent php developments, consider updating this test.&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Once again, we use a descriptive message to explain to our user what is going on. We use a return statement, as we do not want to run the rest of the method,
and its generally considered cleaner than an &lt;code>else&lt;/code> statement.&lt;/p>
&lt;p>&lt;strong>Source classes must be final&lt;/strong>&lt;/p>
&lt;p>Lets assume we want to make all our classes final (except for a few, but we&amp;rsquo;ll get back to that). This is useful when you are writing a library and don&amp;rsquo;t want
users to extend your classes.&lt;/p>
&lt;p>Reflection classes have a method named &lt;code>isFinal&lt;/code>, but we may have abstract classes, traits and interface, which can not be final, so that won&amp;rsquo;t be enough.
To get around this we write a provider that gives us only the &amp;lsquo;concrete&amp;rsquo; classes.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideConcreteSourceClasses&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$item&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">$item&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getConcreteSrcClasses&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">getConcreteSrcClasses&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">array_filter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">getSrcClasses&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$class&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$rc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">\ReflectionClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$class&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="o">!&lt;/span>&lt;span class="nv">$rc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">isInterface&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="o">!&lt;/span>&lt;span class="nv">$rc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">isAbstract&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="o">!&lt;/span>&lt;span class="nv">$rc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">isTrait&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>We filter out all classes that are interfaces, abstract or traits, leaving us with just the concrete classes. And create the following test, to make sure new classes are final.&lt;/p>
&lt;p>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @dataProvider provideConcreteSourceClasses
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string $className
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">test_all_classes_are_final&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$rc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">\ReflectionClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$rc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">isFinal&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Source class &amp;#34;%s&amp;#34; should final.&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
But what if we have classes that we want users to extend some of our non abstract classes? Lets create a filter for that.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * This array contains all classes that are extension points.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @var string[]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="nv">$extensionPoints&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ExtenableClass&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">OtherExtendableClass&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @dataProvider provideConcreteSourceClasses
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string $className
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">test_all_classes_are_final&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$rc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">\ReflectionClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">in_array&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="nv">$extensionPoints&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">addToAssertionCount&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$rc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">isFinal&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Source class &amp;#34;%s&amp;#34; should final.&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>But of course, it doesn&amp;rsquo;t end here, we now also want to check if the list of extension points is still valid&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @dataProvider provideExtensionPoints
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string $className
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">test_non_final_non_extension_list_is_valid&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$rc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">\ReflectionClass&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">!&lt;/span>&lt;span class="nv">$rc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">isFinal&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Source class &amp;#34;%s&amp;#34; an extension point and should not be made final.&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">provideExtensionPoints&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="k">array&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">array_map&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">static&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$item&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nv">$item&lt;/span>&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="nv">$extensionPoints&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>These two tests now make sure that all our classes are final, except for a specific list, and we also make sure that the list is still valid.&lt;/p>
&lt;p>&lt;strong>Source classes must have unit tests&lt;/strong>&lt;/p>
&lt;p>Now this is a funny one, as you are testing if your code has tests. This also makes it easy to spot if a big PR is missing unit tests for certain classes.&lt;/p>
&lt;p>The following method will check if all source classes have corresponding unit tests, except those that are in a &amp;rsquo;legacy&amp;rsquo; list
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * This array contains all classes that are not yet unit tested due to legacy reasons.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * This list should never be added to, only removed from.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @var string[]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">private&lt;/span> &lt;span class="k">static&lt;/span> &lt;span class="nv">$nonTestedConcreteClasses&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">NotTestedClass&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">OtherClassWithoutTests&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="na">class&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">];&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd">/**
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @dataProvider provideConcreteSourceClasses
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> *
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> * @param string $className
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">test_all_concrete_classes_have_tests&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">string&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$testClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">preg_replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/VendorNamespace/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;VendorNamespace\\Test&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s1">&amp;#39;Test&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">\in_array&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="nv">$nonTestedConcreteClasses&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertFalse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">class_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$testClass&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Class &amp;#34;%s&amp;#34; has a corresponding unit test &amp;#34;%s&amp;#34;, and can be removed from the non tested class list&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$testClass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">markTestSkipped&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Class &amp;#34;%s&amp;#34; does not have a corresponding unit test yet, you can improve this by adding one&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">class_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$testClass&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Class &amp;#34;%s&amp;#34; doesn\&amp;#39;t not have a corresponding unit test &amp;#34;%s&amp;#34;, please add one&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$testClass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/p>
&lt;p>We use the dataprovider we created when making sure all classes are final, as we don&amp;rsquo;t usually have unit tests for interfaces or abstract classes.&lt;/p>
&lt;p>Lets walk through the method and see what every part does, starting with the &lt;code>preg_replace&lt;/code>.&lt;/p>
&lt;p>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$testClass&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">preg_replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;/VendorNamespace/&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;VendorNamespace\\Test&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$className&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s1">&amp;#39;Test&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
Here, &lt;code>VendorNamespace&lt;/code> is once again the &amp;lsquo;default&amp;rsquo; namespace, as mentioned in &lt;a href="#getting-the-classes">Getting the classes&lt;/a>.
We manipulate the FQN we got to change &lt;code>VendorNamespace&lt;/code> into &lt;code>VendorNamespace\\Test&lt;/code>, and then append Test to it. For a lot of projects, this is how
tests are named, simply the same class name, but with Test appended to it.
We use &lt;code>preg_replace&lt;/code>, with a &lt;code>1&lt;/code> as the 3rd parameter, to make sure we only do this once, on the first occurrence.
As your &lt;code>VendorNamespace&lt;/code> may appear elsewhere within the namespace.
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">\in_array&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$className&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">self&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="nv">$nonTestedConcreteClasses&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertFalse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">class_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$testClass&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Class &amp;#34;%s&amp;#34; has a corresponding unit test &amp;#34;%s&amp;#34;, and can be removed from the non tested class list&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$testClass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">markTestSkipped&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Class &amp;#34;%s&amp;#34; does not have a corresponding unit test yet, you can improve this by adding one&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
First we make sure that our list of not tested classes is still valid, by making asserting they are not tested, and otherwise notifying the user to
update the list.&lt;/p>
&lt;p>Next up, we mark the test as skipped. If you add &lt;code>verbose=&amp;quot;true&amp;quot;&lt;/code> to your &lt;code>phpunit.xml&lt;/code>, or run it with the &lt;code>--verbose&lt;/code> flag,
all skipped tests will show their message. Meaning you will see exactly what source classes do not have unit tests every time you run those tests.
This will make sure you always have your technical debt in sights.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">class_exists&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$testClass&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">sprintf&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Class &amp;#34;%s&amp;#34; doesn\&amp;#39;t not have a corresponding unit test &amp;#34;%s&amp;#34;, please add one&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$className&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$testClass&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>And to end it, we make sure that our classes in fact do have the unit test.&lt;/p>
&lt;h2 id="whats-next">What&amp;rsquo;s next&lt;/h2>
&lt;p>First off, i would highly recommend checking out the
&lt;a href="https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/aabbbc2021e2222f1d49435bc8f4e3467d08c31a/tests/AutoReview" target="_blank" rel="noopener">AutoReview tests from PHP-CS-FIXER&lt;/a>,
where these tests originated. They were originally created by Dariusz Rumiński (keradus), who you should check out on &lt;a href="https://github.com/keradus" target="_blank" rel="noopener">github&lt;/a>.
By now
&lt;a href="https://github.com/opencfp/opencfp/blob/e45b1f263e429ea3a85f8bbdf7e4a8932e153aec/tests/Unit/ProjectCodeTest.php" target="_blank" rel="noopener">other&lt;/a>
&lt;a href="https://github.com/infection/infection/tree/cdd179bfd1c6d895a2559667ca8030d12a2feeb9/tests/AutoReview" target="_blank" rel="noopener">projects&lt;/a> use these new types of tests as well.&lt;/p>
&lt;p>For these checks, the options are almost endless, by using reflection, juggling some name spaces, and other methods, a lot can be
tested before a PR has even been made. You could
&lt;a href="https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/aabbbc2021e2222f1d49435bc8f4e3467d08c31a/tests/AutoReview/ProjectCodeTest.php#L89" target="_blank" rel="noopener">test classes don&amp;rsquo;t have public methods not coming from an interface&lt;/a>,
or make sure files like &lt;code>.travis.yml&lt;/code> and &lt;code>appveyor.yml&lt;/code>
&lt;a href="https://github.com/infection/infection/blob/cdd179bfd1c6d895a2559667ca8030d12a2feeb9/tests/AutoReview/BuildConfigYmlTest.php" target="_blank" rel="noopener">contain valid yaml&lt;/a>.
You can even check if &lt;a href="https://github.com/webmozart/assert/pull/75" target="_blank" rel="noopener">the readme is up to date&lt;/a>.&lt;/p>
&lt;p>The most important part is that your error messages inform users why a test is suddenly failing, and what they can do to fix it,
like adding another unit test, or making a property protected or private. And make sure that you cache what results you can when you run
multiple tests against the same data set.&lt;/p>
&lt;p>And last but not least, add an extra test set to make sure that your providers are valid. There
are a lot of edge cases, so making sure that your lists of exceptions to the rules etc are valid,
and that your providers/exception lists are valid helps a long way to reduce harder to debug error messages.&lt;/p>
&lt;p>So go out there, and make your life a little bit easier by automating (part of) your code reviews!&lt;/p></description></item><item><title>The case against case</title><link>https://backendtea.com/post/the-case-against-case/</link><pubDate>Mon, 18 Jun 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/the-case-against-case/</guid><description>&lt;p>Chances are you have written a switch case statement or two. Aren&amp;rsquo;t they much &amp;lsquo;cleaner&amp;rsquo; than a bunch of if else statements?
Today i would like to convince you that using switch case, in modern php, is a bad practice.&lt;/p>
&lt;p>Lets take a look at the following code snippet.&lt;/p>
&lt;p>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//test.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">declare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strict_types&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">One&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__toString&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">switch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">echo&lt;/span> &lt;span class="s2">&amp;#34;got 1 &lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">default&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">echo&lt;/span> &lt;span class="s2">&amp;#34;did not get 1&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">One&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ php test.php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">got &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">got &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">got &lt;span class="m">1&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
As we can see, switch case checks if its value is equal rather than the same, it uses &lt;code>==&lt;/code> instead of &lt;code>===&lt;/code>.
This means switch case will juggle types, in order to see if the values it has are &amp;rsquo;equal&amp;rsquo;. Even the &lt;code>declare(strict_types=1)&lt;/code> doesn&amp;rsquo;t change anything about that.&lt;/p>
&lt;p>Our code bases are filled with &lt;code>===&lt;/code> checks, we use &lt;code>declare(strict_types=1)&lt;/code> in our php 7+ projects. If possible we enforce both of those in our code standards.
Yet, we still use switch and case, while very case is an equal check rather than a same check. Which means its a potential for bugs/ unexpected behavior.&lt;/p>
&lt;p>So, if you are using switch case, refactor it into an if/else statement like so:&lt;/p>
&lt;p>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">//test.php
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">declare&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">strict_types&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">One&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="fm">__toString&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">function&lt;/span> &lt;span class="nf">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$value&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$value&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">echo&lt;/span> &lt;span class="s2">&amp;#34;got 1 &lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">echo&lt;/span> &lt;span class="s2">&amp;#34;did not get 1&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="nx">One&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;1&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ php test.php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">did not get &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">did not get &lt;span class="m">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">got &lt;span class="m">1&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/p>
&lt;p>And suddenly, we are checking that the input is actually the string &amp;lsquo;1&amp;rsquo;, rather than something that is roughly the same.
Of course, this is a rather simple example, but any switch case statement can be changed into an if else that will not type juggle.
Another benefit of this is that magic methods like &lt;code>__toString()&lt;/code> will not be called, just in case, god forbid, those have side effects.&lt;/p>
&lt;p>Maybe if we are lucky we&amp;rsquo;ll see strict switch statements in &lt;a href="https://github.com/sgolemon/php-src/commit/65afc5fbe53b56dff54b0ccf51a747ce1d561534" target="_blank" rel="noopener">future php versions&lt;/a>.
But until that day, lets stay away from switch case.&lt;/p></description></item><item><title>PHP extensions, polyfills and you</title><link>https://backendtea.com/post/extension-polyfill/</link><pubDate>Tue, 12 Jun 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/extension-polyfill/</guid><description>&lt;p>A little while ago someone ran into an error with a dependency of a project i worked on.
A fatal error, which seemingly only occurred for them: the &lt;code>ctype_alnum&lt;/code> function was not defined.&lt;/p>
&lt;p>So it turned out the &lt;code>ctype&lt;/code> functions aren&amp;rsquo;t part of the php core, but are instead a &amp;lsquo;default&amp;rsquo; extension.
Lets explore what extensions mean for your project, and how to help your users with these kinds of errors.&lt;/p>
&lt;h2 id="extensions">Extensions&lt;/h2>
&lt;p>You may have seen something like this in a &lt;code>composer.json&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;require&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ext-intl&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;*&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This means the project needs the &lt;code>intl&lt;/code> php extension in order to work. If you don&amp;rsquo;t have it installed, composer will not install the packages, and
tell you it is missing an extension it needs to function.
Generally, installing a package is as easy as &lt;code>sudo apt-get install php-intl&lt;/code>, and restart your web server if needed. A lot of deploy/build environments will
automatically install the needed extensions, so no need to worry about that.&lt;/p>
&lt;p>Even running composer already requires the simple-xml extension, so its not like you can run php without any extensions.
A lot of packages require extensions without making it explicit like this, which is a shame.
Getting an error when running &lt;code>composer install&lt;/code> that mentions an extension being needed is a lot more informative than seeing the following error
when running your program.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Fatal error: Uncaught Error: Call to undefined function Composer\XdebugHandler\ctype_alnum() in $dataRec-&amp;gt;vendor/composer/xdebug-handler/src/XdebugHandler.php on line 437
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A closer look at the
reveals that from php 4.2 and onward, it is enabled by default.
Meaning you would have to pass the &lt;code>--disable-ctype&lt;/code> flag when compiling php to disable it. However, certain php distributions don&amp;rsquo;t include it, as they did in fact
pass that flag while compiling, some examples being FreeBSD and Alpine. So relying on the fact that it is enabled by &amp;lsquo;default&amp;rsquo; is not really an option.&lt;/p>
&lt;p>There are
of extensions available. Some are part of the &amp;lsquo;core&amp;rsquo; meaning you can&amp;rsquo;t disable them,
others are &amp;lsquo;default&amp;rsquo;, meaning a &amp;rsquo;normal&amp;rsquo; php installation &lt;em>should&lt;/em> have it, but as mentioned early, we can&amp;rsquo;t rely on that.
Some need to be installed by hand, and some even need a whole lot of configuration.
So before you start requiring extensions left and right, it may be a good idea to check how much strain it will put on your users.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="polyfills">Polyfills&lt;/h2>
&lt;p>A way to avoid a direct dependency on (some) extensions is using a
.
Symfony provides quite a few, but there are others as well. So instead of requiring the ctype extension, we can require its polyfill.
Composer can install that itself, so no error for the user, and if the user doesn&amp;rsquo;t have the extension available it will be &amp;lsquo;suggested&amp;rsquo; by composer.
Of course, this suggestion is buried by dozens of other suggestions that other packages make.&lt;/p>
&lt;p>The one down side of using a polyfill is that it is generally slower. However, if you don&amp;rsquo;t have control over your production server, these polyfills mean you can still use the functionality they provide.&lt;/p>
&lt;h2 id="and-you">And you&lt;/h2>
&lt;p>So, what can you do with this information?&lt;/p>
&lt;p>If you have an application for which performance is absolutely critical, consider checking what polyfills you are actually using, and what extensions are
available on your production server. Even if you don&amp;rsquo;t use &lt;code>ctype&lt;/code> or &lt;code>intl&lt;/code> or another extension directly, your dependencies may use them, so getting the
&amp;lsquo;real&amp;rsquo; extension could give you a performance boost. This boost will be absolutely minimal, unless you use these functions a ton.&lt;/p>
&lt;p>If you maintain an (open source) package, take a closer look at your code base,
what extensions are you actually using, and are they presented in your &lt;code>composer.json&lt;/code> ?
The one line that says &lt;code>&amp;quot;ext-intl&amp;quot;: &amp;quot;*&amp;quot;&lt;/code> can save someone hours of debugging. Even when dealing with a &amp;lsquo;default&amp;rsquo; extension, like &lt;code>ctype&lt;/code>,
it can an will be disabled on certain systems. If there is a polyfill available for that extension, you can also choose to require a polyfill instead.&lt;/p>
&lt;p>You can also choose to work around any extensions that aren&amp;rsquo;t part of the core, but why would you? I&amp;rsquo;m not saying you should require every possible
extension. Especially ones that are harder to install for your users, but most extensions should have better performance than the php implementation you
could write yourself. But anything is better than requiring an extensions in your code, but not reflecting that in your &lt;code>composer.json&lt;/code>&lt;/p></description></item><item><title>Test your tests are Testing</title><link>https://backendtea.com/post/test-your-tests-are-testing/</link><pubDate>Sun, 10 Jun 2018 00:00:00 +0000</pubDate><guid>https://backendtea.com/post/test-your-tests-are-testing/</guid><description>&lt;p>Are your tests testing? Are you assertions asserting?&lt;/p>
&lt;p>Let&amp;rsquo;s find out how we can test that our tests are testing, with the mutation testing framework: &lt;a href="https://github.com/infection/infection" target="_blank" rel="noopener">infection&lt;/a>.&lt;/p>
&lt;p>Imagine this, you spend the last few hour refactoring a big part of the code base. You made some big changes and a lot of logic has been completely changed.
Now it&amp;rsquo;s time to figure out what tests have broken due to all the refactoring.
You open up the terminal and enter the good old &lt;code>vendor/bin/phpunit&lt;/code> and watch the tests run.&lt;/p>
&lt;pre>$ vendor/bin/phpunit
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.
Runtime: PHP 7.1.18
Configuration: /home/me/Projects/Important-Project/phpunit.xml
............................................................ 60 / 108 ( 55%)
................................................ 108 / 108 (100%)
Time: 24 ms, Memory: 4.00MB
&lt;span style="background-color:#859900">&lt;font color="#073642">OK (108 tests, 187 assertions)&lt;/font>&lt;/span>
&lt;/pre>
&lt;p>That doesn&amp;rsquo;t look right, at least a few tests should have been broken by those changes. Some exceptions are no longer being thrown, return types have changed
yet, your tests don&amp;rsquo;t care. According to them everything is still just as it was.&lt;/p>
&lt;p>Thankfully, we have access to mutation testing, which will help you make sure that your tests are in fact, testing.&lt;/p>
&lt;div id="floater" class="not-prose">
&lt;script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CW7DC2JI&amp;placement=backendteacom&amp;format=cover" id="_carbonads_js">&lt;/script>
&lt;/div>
&lt;h2 id="what-is-mutation-testing">What is mutation testing&lt;/h2>
&lt;p>Have you ever created a unit test, and then changed something in your code to see if it failed? Well mutation testing is exactly that, but automated.&lt;/p>
&lt;p>Let&amp;rsquo;s say you have the following class you are testing
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Calculator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">adds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$b&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nv">$b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">subtracts&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">int&lt;/span> &lt;span class="nv">$a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">int&lt;/span> &lt;span class="nv">$b&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nv">$a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nv">$b&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/p>
&lt;p>And the following test to make sure it works&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CalculatorTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">test_adds&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$calc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Calculator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">is_int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$calc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">adds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">test_subtracts&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$calc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Calculator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">is_int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$calc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">subtracts&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>We have 100% coverage, tests are green, so everything works as intended, right?&lt;/p>
&lt;p>Of course, the &lt;code>subtracts&lt;/code> method has a bug, instead of a &lt;code>-&lt;/code> there is a &lt;code>+&lt;/code>. A simple copy paste error, but our tests aren&amp;rsquo;t catching it.&lt;/p>
&lt;p>So, how do we make sure our tests will catch those bugs? Through mutation testing.
PHP has the mutation testing framework Infection, which is what we will be using for the example.&lt;/p>
&lt;p>Let&amp;rsquo;s look at the interesting parts of the output:&lt;/p>
&lt;pre>
&lt;b>4&lt;/b> mutations were generated:
&lt;b> 2&lt;/b> mutants were killed
&lt;b> 0&lt;/b> mutants were not covered by tests
&lt;b> 2&lt;/b> covered mutants were not detected
&lt;b> 0&lt;/b> errors were encountered
&lt;b> 0&lt;/b> time outs were encountered
Metrics:
Mutation Score Indicator (MSI): &lt;font color="#B58900">&lt;b>50%&lt;/b>&lt;/font>
Mutation Code Coverage: &lt;font color="#859900">&lt;b>100%&lt;/b>&lt;/font>
Covered Code MSI: &lt;font color="#B58900">&lt;b>50%&lt;/b>&lt;/font>
&lt;/pre>
&lt;p>Four mutations were created, but only two were killed, while the other two escaped. Lets look at &lt;code>infection-log.txt&lt;/code> to see what has happened:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">Escaped mutants:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">================
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1) /home/gpagter/Projects/Randoms/calculator/src/Calculator.php:8 [M] Plus
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">--- Original
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+++ New
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span>&lt;span class="gu">@@ @@
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> public function adds(int $a, int $b) : int
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- return $a + $b;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ return $a - $b;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> public function subtracts(int $a, int $b) : int
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2) /home/gpagter/Projects/Randoms/calculator/src/Calculator.php:13 [M] Plus
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">--- Original
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+++ New
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span>&lt;span class="gu">@@ @@
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> public function subtracts(int $a, int $b) : int
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- return $a + $b;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ return $a - $b;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Killed mutants:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">===============
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1) /home/gpagter/Projects/Randoms/calculator/src/Calculator.php:6 [M] PublicVisibility
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">--- Original
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+++ New
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span>&lt;span class="gu">@@ @@
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> class Calculator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- public function adds(int $a, int $b) : int
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ protected function adds(int $a, int $b) : int
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return $a + $b;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2) /home/gpagter/Projects/Randoms/calculator/src/Calculator.php:11 [M] PublicVisibility
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">--- Original
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+++ New
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span>&lt;span class="gu">@@ @@
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return $a + $b;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">- public function subtracts(int $a, int $b) : int
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gd">&lt;/span>&lt;span class="gi">+ protected function subtracts(int $a, int $b) : int
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return $a + $b;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Let&amp;rsquo;s dissect what has happened, and see what it means for us.&lt;/p>
&lt;p>When looking at the Killed mutants, we see two mutants, both &lt;code>PublicVisibility&lt;/code>. What infection has done for these methods, is change their signature from public to protected.
And then it ran all tests that are relevant for that line of code. The tests then failed, which means that the mutation was &amp;lsquo;killed&amp;rsquo;. Our tests detected this change in the code.&lt;/p>
&lt;p>But what about the two &amp;rsquo;escaped&amp;rsquo; mutants? Our tests did not pick up on this change.
When the &lt;code>+&lt;/code> got changed to a &lt;code>-&lt;/code>, our tests did not fail, all assertions were okay. This means that the mutant has &amp;rsquo;escaped&amp;rsquo;. The tests did not pick up on the
fact that our code changed.&lt;/p>
&lt;p>If a test took too long, or caused a PHP error, it is also considered killed. The last option are mutants that are not covered by tests. Infection still takes these into account for &lt;a href="#metrics">metrics&lt;/a>.&lt;/p>
&lt;p>Lets go ahead and update our tests to kill these mutants.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-php" data-lang="php">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;?&lt;/span>&lt;span class="nx">php&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CalculatorTest&lt;/span> &lt;span class="k">extends&lt;/span> &lt;span class="nx">TestCase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">test_adds&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$calc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Calculator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$calc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">adds&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">is_int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$result&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$result&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">public&lt;/span> &lt;span class="k">function&lt;/span> &lt;span class="nf">test_subtracts&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$calc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="nx">Calculator&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nv">$calc&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">subtracts&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertTrue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">is_int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">$result&lt;/span>&lt;span class="p">));&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="na">assertSame&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nv">$result&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Now we find that our &lt;code>subtracts&lt;/code> method is actually returning 3 instead of -1. So we fix the bug, and run our tests again. They all pass, so lets run infection again as well.&lt;/p>
&lt;pre>
&lt;b>4&lt;/b> mutations were generated:
&lt;b> 4&lt;/b> mutants were killed
&lt;b> 0&lt;/b> mutants were not covered by tests
&lt;b> 0&lt;/b> covered mutants were not detected
&lt;b> 0&lt;/b> errors were encountered
&lt;b> 0&lt;/b> time outs were encountered
Metrics:
Mutation Score Indicator (MSI): &lt;font color="#859900">&lt;b>100%&lt;/b>&lt;/font>
Mutation Code Coverage: &lt;font color="#859900">&lt;b>100%&lt;/b>&lt;/font>
Covered Code MSI: &lt;font color="#859900">&lt;b>100%&lt;/b>&lt;/font>
&lt;/pre>
&lt;p>We did it, we fixed the bug, killed all mutants and saved the day!&lt;/p>
&lt;h2 id="metrics">Metrics&lt;/h2>
&lt;p>We have seen the Metrics in the infection output, but what does it mean?&lt;/p>
&lt;p>&lt;strong>Mutation Score Indicator (MSI)&lt;/strong>&lt;/p>
&lt;p>Let&amp;rsquo;s say our code base has a total of 100 possible mutations. 40 of those are caught by the tests, either because the tests fail or because
they cause a time out or a php error. 20 of them are not covered by tests, and another 40 don&amp;rsquo;t cause the tests to fail and &amp;rsquo;escape&amp;rsquo;.&lt;/p>
&lt;p>This would give us a MSI of 40% (The amount of killed mutations divided by the total possible amount.) You generally want this number
to be close to your code coverage. If it is a lot lower it means your tests aren&amp;rsquo;t as good as you think they are.&lt;/p>
&lt;p>It can be enforced, for example on CI, with the &lt;code>--min-msi&lt;/code> flag.&lt;/p>
&lt;p>&lt;strong>Mutation Code Coverage&lt;/strong>&lt;/p>
&lt;p>This the amount of mutations that are covered by tests, if we take the numbers of the MSI example, it would be 80%.&lt;/p>
&lt;p>&lt;strong>Covered Code MSI&lt;/strong>&lt;/p>
&lt;p>This is the same as MSI, except only for covered code. So all mutations that are not covered by tests are ignored.
If we take the numbers of the MSI example, we have a Covered Code MSI of 50%. You want this to be as close to 100% as possible.&lt;/p>
&lt;p>It can be enforced, for example on CI, with the &lt;code>--min-covered-msi&lt;/code> flag.&lt;/p>
&lt;h2 id="usage">Usage&lt;/h2>
&lt;p>&lt;strong>Installation&lt;/strong>&lt;/p>
&lt;p>You can install infection in a few ways:&lt;/p>
&lt;ul>
&lt;li>Through Composer as a dev dependency: &lt;code>composer require infection/infection --dev&lt;/code>&lt;/li>
&lt;li>Through Composer as a global package: &lt;code>composer global require infection/infection&lt;/code>&lt;/li>
&lt;li>Or as a phar:&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ wget https://github.com/infection/infection/releases/download/0.8.2/infection.phar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ wget https://github.com/infection/infection/releases/download/0.8.2/infection.phar.pubkey
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ chmod +x infection.phar
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want to update your phar you can do so by running &lt;code>$ ./infection.phar self-update&lt;/code>.&lt;/p>
&lt;p>&lt;strong>Local usage&lt;/strong>&lt;/p>
&lt;p>Running infection can take a lot of time if you have a big project or if your tests are slow. One way to speed it up is to run it with the following option:
&lt;code>--threads=`nproc`&lt;/code>. This will run the tests against mutations multi threaded, with as much threads as useful. &lt;strong>Note:&lt;/strong> This should only be done
if your tests can be ran parallel. If they use a database or the file system this can lead to a lot of mutations being killed that are not detected by your tests.&lt;/p>
&lt;p>&lt;strong>CI&lt;/strong>&lt;/p>
&lt;p>If you run infection during CI, its recommended to run it with the &lt;code>--min-msi&lt;/code> and/or &lt;code>--min-covered-msi&lt;/code>. This will force you to write better tests and keep your
MSI and Covered Code MSI as a stable rate.&lt;/p>
&lt;p>If you are already generation coverage on CI, you can hand infection that coverage, so it doesn&amp;rsquo;t have to generate it itself.
Infection needs xml and junit coverage to run&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ vendor/bin/phpunit --coverage-xml coverage/coverage-xml --log-junit&lt;span class="o">=&lt;/span>coverage/phpunit.junit.xml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ vendor/bin/infection --coverage&lt;span class="o">=&lt;/span>coverage&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>If you only want to run infection against changed code, you can run it like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">INFECTION_FILTER&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>git diff &lt;span class="si">${&lt;/span>&lt;span class="nv">GIT_PREVIOUS_SUCCESSFUL_COMMIT&lt;/span>&lt;span class="k">:-&lt;/span>&lt;span class="nv">origin&lt;/span>&lt;span class="p">/master&lt;/span>&lt;span class="si">}&lt;/span> &lt;span class="nv">$GIT_COMMIT&lt;/span> --name-only &lt;span class="p">|&lt;/span> grep /src/ &lt;span class="p">|&lt;/span> paste -sd &lt;span class="s2">&amp;#34;,&amp;#34;&lt;/span> -&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ vendor/bin/infection --threads&lt;span class="o">=&lt;/span>&lt;span class="m">4&lt;/span> --min-msi&lt;span class="o">=&lt;/span>&lt;span class="m">70&lt;/span> --only-covered --filter&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">${&lt;/span>&lt;span class="nv">INFECTION_FILTER&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> --ignore-msi-with-no-mutations&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This will only run infection against changed files that differ from the last successful commit on this branch, or master if there is none, in the &lt;code>src&lt;/code> folder.
The &lt;code>--ignore-msi-with-no-mutations&lt;/code> flag causes us to not error on min msi when we have 0 mutations.&lt;/p>
&lt;p>For all the configuration options, you can check out &lt;a href="https://infection.github.io/guide/usage.html" target="_blank" rel="noopener">infections documentation&lt;/a>.&lt;/p></description></item></channel></rss>