<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrej Rypo</title>
    <description>The latest articles on DEV Community by Andrej Rypo (@dakujem).</description>
    <link>https://dev.to/dakujem</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F447512%2Fb195e4b7-aed1-4ccc-b6bc-a9c2b3e350d6.jpeg</url>
      <title>DEV Community: Andrej Rypo</title>
      <link>https://dev.to/dakujem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dakujem"/>
    <language>en</language>
    <item>
      <title>Why I Built Migrun</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Sat, 28 Mar 2026 20:17:40 +0000</pubDate>
      <link>https://dev.to/dakujem/why-i-built-migrun-2moa</link>
      <guid>https://dev.to/dakujem/why-i-built-migrun-2moa</guid>
      <description>&lt;p&gt;I mostly work with PHP projects that do not live inside a full framework like Laravel or Symfony — frameworks that come with their own ORM-based migration tools.&lt;br&gt;
The existing standalone migration tools like Phinx, Phpmig, Phoenix are available, but they come with problems that kept bothering me over the years.&lt;br&gt;
I would not even call Phinx standalone — it pulls in the Cake framework's core.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service container integration is a hack.&lt;/strong&gt; At least in Phinx, which I used the most.&lt;br&gt;
If you use a dependency injection container (and you &lt;em&gt;really&lt;/em&gt; should), wiring services into Phinx migrations is not possible.&lt;br&gt;
People resort to global static access or other workarounds to get their own database connection or a service like logger into their migration.&lt;br&gt;
This completely goes against modern DI practices.&lt;br&gt;
I wanted migrations to simply declare their dependencies and have them resolved from the container automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query builders add complexity you don't need.&lt;/strong&gt;&lt;br&gt;
Migration systems often provide table &lt;em&gt;builders&lt;/em&gt; — APIs for creating columns, indexes, and tables in a database-agnostic way.&lt;br&gt;
Usually based on the particular framework ORM. In theory, this lets you switch databases without rewriting migrations. In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most projects never switch databases. None of the projects I've worked with ever did. They &lt;em&gt;introduced&lt;/em&gt; new databases, but did not &lt;em&gt;replace&lt;/em&gt; one with another.&lt;/li&gt;
&lt;li&gt;When they do, they have to deal with database-specific features, data types, and behaviors anyway.&lt;/li&gt;
&lt;li&gt;The builder APIs cannot cover every database-specific case. &lt;a href="https://stackoverflow.com/questions/45803634/create-a-tiny-integer-column-with-custom-size-in-laravel-migration" rel="noopener noreferrer"&gt;Like this classic problem in Laravel&lt;/a&gt;. Phinx is even more limited. You end up writing raw SQL for the tricky parts regardless.&lt;/li&gt;
&lt;li&gt;The builder adds a layer of abstraction and complexity for something that a simple &lt;code&gt;CREATE TABLE&lt;/code&gt; statement does just fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am not saying builders are useless. But for many projects, they are unnecessary overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All the tools use inheritance.&lt;/strong&gt; Your migration class must extend an &lt;code&gt;AbstractMigration&lt;/code&gt; base class. This means you are locked into a class hierarchy.&lt;br&gt;
In modern PHP, we prefer composition and interfaces over inheritance. Inheritance-based designs are rigid and harder to integrate with your own architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They all impose naming conventions&lt;/strong&gt; and rigid directory structure upon the adopters. Archiving migrations after hundreds have accumulated? Not an option or PITA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The standalone tools feel outdated.&lt;/strong&gt; Migration tools in the PHP ecosystem have not evolved with the language.&lt;br&gt;
PHP has readonly classes, enums, named arguments, union types, fibers, pipelines.&lt;br&gt;
Meanwhile, migration tools still use patterns from the PHP 5 era.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any sort of I/O integration is a hack.&lt;/strong&gt;&lt;br&gt;
Phinx only works within Symfony Console, and the runner is tightly coupled to it. Go and try to create an HTML page with the list of migrations, I dare you.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I wanted
&lt;/h2&gt;

&lt;p&gt;Something dead simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An interface for migrations. Implement &lt;code&gt;up()&lt;/code&gt;, optionally &lt;code&gt;down()&lt;/code&gt;. Done.&lt;/li&gt;
&lt;li&gt;Autowiring from the service container. Declare &lt;code&gt;PDO $db&lt;/code&gt; as a parameter and it gets resolved.&lt;/li&gt;
&lt;li&gt;No base classes to extend.&lt;/li&gt;
&lt;li&gt;No config files.&lt;/li&gt;
&lt;li&gt;No bundled CLI. No Symfony console.&lt;/li&gt;
&lt;li&gt;No query builder.&lt;/li&gt;
&lt;li&gt;No file naming constraints.&lt;/li&gt;
&lt;li&gt;Zero runtime dependencies. Not even utility packages. No Cake core.&lt;/li&gt;
&lt;li&gt;Any database, any tech stack, any service container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/dakujem/migrun" rel="noopener noreferrer"&gt;&lt;strong&gt;Migrun&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Who it is for
&lt;/h2&gt;

&lt;p&gt;Migrun is for developers who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work on PHP projects outside of Laravel or Doctrine ecosystems&lt;/li&gt;
&lt;li&gt;Want a migration runner that fits into their existing architecture, not one that dictates it&lt;/li&gt;
&lt;li&gt;Prefer writing SQL directly instead of learning a builder API&lt;/li&gt;
&lt;li&gt;Use a service container and want their migrations to benefit from it naturally&lt;/li&gt;
&lt;li&gt;Want something minimal and hackable, not a full framework&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you already use Laravel or Doctrine, their migration systems are deeply integrated and you should keep using them.&lt;br&gt;
Migrun was not built to replace those — it was built for everyone else.&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Return an anonymous class instance from a PHP file. Get all the parameters auto-resolved from your service container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// 20260326_181000_a_migration.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Dakujem\Migrun\Migration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Psr\Log\LoggerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;PDO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;?PDO&lt;/span&gt; &lt;span class="nv"&gt;$db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;?LoggerInterface&lt;/span&gt; &lt;span class="nv"&gt;$logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;&amp;lt;&amp;lt;&amp;lt;SQL

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE
)

SQL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Users table created.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Need a rollback? Don't like the heredoc syntax? No problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// 20260326_181002_reversible_migration.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Dakujem\Migrun\ReversibleMigration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;PDO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ReversibleMigration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;?PDO&lt;/span&gt; &lt;span class="nv"&gt;$db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CREATE INDEX idx_users_email ON users (email)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;?PDO&lt;/span&gt; &lt;span class="nv"&gt;$db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DROP INDEX idx_users_email'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also just return a closure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// 20260326_181008_closure_migration.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;\PDO&lt;/span&gt; &lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$db&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;&amp;lt;&amp;lt;&amp;lt;SQL

CREATE INDEX idx_users_email ON users (email)

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

&lt;/div&gt;



&lt;p&gt;You write the SQL on your terms. You run whatever services your app needs. Fully in control. No hacks.&lt;/p&gt;

&lt;p&gt;Setting it up takes a few lines of code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$migrun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MigrunBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/migrations'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// any PSR-11 container for autowiring&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;pdoStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PDO&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$migrun&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;          &lt;span class="c1"&gt;// run pending migrations&lt;/span&gt;
&lt;span class="nv"&gt;$migrun&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// roll back the last one&lt;/span&gt;
&lt;span class="nv"&gt;$migrun&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;       &lt;span class="c1"&gt;// see what's applied and what's pending&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://github.com/dakujem/migrun" rel="noopener noreferrer"&gt;readme&lt;/a&gt; for fully functional CLI examples (like &lt;code&gt;composer migrate:up&lt;/code&gt;),&lt;br&gt;
or have AI tools set them up for you in a minute.&lt;br&gt;
Symfony console is supported but not required (e.g. &lt;code&gt;php bin/console db:migrate&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;Migrun is built around four straight-forward interfaces that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;look for migrations&lt;/li&gt;
&lt;li&gt;execute migrations&lt;/li&gt;
&lt;li&gt;resolve migration dependencies&lt;/li&gt;
&lt;li&gt;store migration history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to load pure SQL files? Implement a new "executor" (a single method) and a new "finder" (two methods).&lt;br&gt;
Want to use a different DI container? Implement a single "invoker" method.&lt;br&gt;
Want to store the history in Redis? Implement a new storage class.&lt;/p&gt;

&lt;p&gt;No inheritance anywhere in the library. No static methods. No global state.&lt;/p&gt;

&lt;p&gt;Give it a try.&lt;br&gt;
It's in beta phase — your feedback may shape the release version.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/dakujem/migrun" rel="noopener noreferrer"&gt;github.com/dakujem/migrun&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install:&lt;/strong&gt; &lt;code&gt;composer require dakujem/migrun&lt;/code&gt;; or prompt "Install &lt;code&gt;dakujem/migrun&lt;/code&gt; from Packagist using the recommended setup and add CLI scripts called via Composer."&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>database</category>
      <category>devops</category>
    </item>
    <item>
      <title>Stop using jQuery?</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Wed, 25 Jun 2025 08:03:53 +0000</pubDate>
      <link>https://dev.to/dakujem/stop-using-jquery-4480</link>
      <guid>https://dev.to/dakujem/stop-using-jquery-4480</guid>
      <description>&lt;p&gt;I bumped into this article that states jQuery can be replaced by native code: &lt;a href="https://blog.logrocket.com/why-jquery-4-good-reminder-stop-using-jquery/" rel="noopener noreferrer"&gt;Why jQuery 4 is a good reminder to stop using jQuery&lt;/a&gt;&lt;br&gt;
It implies that jQuery has no place in contemporary web development and that the new version serves no practical purpose.&lt;/p&gt;

&lt;p&gt;It is indeed an interesting post.&lt;/p&gt;

&lt;p&gt;Yet, I have to contra with three aspects the post leaves unmentioned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Developer's time is expensive.&lt;/strong&gt; A more concise or fluent API that saves development time makes sense even in the modern age.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DX&lt;/strong&gt;: Looking at the examples how native JS can be used to replace jQuery, I still find the jQuery API more developer friendly. Like method call chaining, manipulating classes of multiple selected elements with a single call, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Legacy apps.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don't get me wrong, I wouldn't use jQuery in a greenfield JS app, but it has its place in the browser environment.&lt;/p&gt;

&lt;p&gt;Modern JS apps tend to opt-in for libs like lodash instead of jQuery to polyfill the utility methods jQuery provides by default.&lt;br&gt;
Some more backend-heavy apps that don't use full-fledged JS frameworks I've worked with recently opted for Alpine instead of jQuery for DOM manipulation.&lt;/p&gt;

&lt;p&gt;Having worked with multiple legacy apps using jQuery as well as apps completely written in Vue or React, this is just my humble experience talking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I actually welcome the new/more-modern jQuery version.&lt;/strong&gt; Replacing jQuery in legacy apps would be pain, so a refreshed version might be a blessing in certain scenarios.&lt;/p&gt;

&lt;p&gt;In years since JS frameworks like React, Angular or Vue became popular, jQuery seems to have earned the disdain of many developers — not because it stopped working, but because fashion moved on.&lt;/p&gt;

&lt;p&gt;What do &lt;em&gt;you think&lt;/em&gt;?&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>jquery</category>
    </item>
    <item>
      <title>How to properly set up WAMP stack with multiple PHP versions in parallel</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Tue, 18 Mar 2025 09:56:35 +0000</pubDate>
      <link>https://dev.to/dakujem/how-to-properly-set-up-wamp-stack-with-multiple-php-versions-in-parallel-32hn</link>
      <guid>https://dev.to/dakujem/how-to-properly-set-up-wamp-stack-with-multiple-php-versions-in-parallel-32hn</guid>
      <description>&lt;p&gt;Ever wondered how to get rid of WampServer, Xampp, EasyPHP and similar stuff?&lt;/p&gt;

&lt;p&gt;In this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to set up PHP, MySQL/MariaDB and Apache on your Windows dev machine (WAMP stack)&lt;/li&gt;
&lt;li&gt;how to run multiple PHP versions on your dev machine without constant switching&lt;/li&gt;
&lt;li&gt;how to set up vhosts and batch scripts to do so efficiently&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;This post is for intermediate PHP developers or someone looking for this particular setup. Don't worry, if different&lt;br&gt;
PHP versions mean nothing to you, or you are not sure what a server is,&lt;br&gt;
it's perfectly okay, just bookmark this post and come back later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Over the years of my beginnings, I used several tools like WampServer, XAMPP or EasyPHP for PHP development. I quickly realised that these tools had one common disadvantage: &lt;strong&gt;Slow release cycle and limited flexibility.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They allowed me to switch PHP versions, but did not allow for multiple PHP versions simultaneously, so I had to constantly switch versions when switching projects (at the time, I would work on several projects for different clients, different servers etc.).&lt;/p&gt;

&lt;p&gt;As an example, at time of writing this, XAMPP only comes bundled with PHP 8.2, while 8.4 has been out for a few months already.&lt;/p&gt;

&lt;p&gt;So what does a developer do when they want to or have to use recent PHP versions?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A sidenote:&lt;br&gt;
This is not about &lt;em&gt;Docker&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Yes, Docker with WSL is a way to go to have the best flexibility, but the setup is more complicated, and there are performance issues that may be circumvented, with drawbacks.&lt;br&gt;
Also, if you are on a Unix compatible platform, just use Docker.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  WAMP stack
&lt;/h2&gt;

&lt;p&gt;It comes down to installing and configuring 3 essential components that "WAMP" stack is composed of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache server (using vhosts)&lt;/li&gt;
&lt;li&gt;MySQL / MariaDB database&lt;/li&gt;
&lt;li&gt;PHP&lt;/li&gt;
&lt;li&gt;P.S., W stands for Windows, hence "WAMP"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is possible to install multiple versions of PHP on a single Windows development machine. A single MySQL or MariaDB version was all I ever needed, but it should be no problem to run different versions in parallel on different ports, if needed be. I'm not sure about installing multiple Apache versions, I never needed those, but then again, it should be possible, serving on different ports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup &amp;amp; Installation
&lt;/h2&gt;

&lt;p&gt;I like to create a dir dedicated to my dev tools, like &lt;code&gt;C:\dev&lt;/code&gt;.&lt;br&gt;
I will be referring to it as such, or just &lt;strong&gt;"the install dir"&lt;/strong&gt;.&lt;br&gt;
Then I keep the projects in a separate dir, let's call it &lt;code&gt;C:\projects&lt;/code&gt; or &lt;strong&gt;"the projects dir"&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Actually, I keep the projects on a separate encrypted drive, but that's out of scope of this article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Stick to the x64 versions of software if your machine is x64 (it will most probably be the case). Do not mix x64 with x32 as some combinations might not work.&lt;/p&gt;

&lt;p&gt;The trick to using several versions is in two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;configuring vhosts for each project (specifying the &lt;code&gt;FCGIWrapper&lt;/code&gt; directive with PHP version of choice)&lt;/li&gt;
&lt;li&gt;setting alias batch commands (.bat files) to invoke different PHP (and composer) executables, e.g. &lt;code&gt;php84&lt;/code&gt;, &lt;code&gt;php82&lt;/code&gt;, &lt;code&gt;php74&lt;/code&gt;, &lt;code&gt;composer@84&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Download &lt;a href="https://windows.php.net/download/" rel="noopener noreferrer"&gt;PHP Windows binaries&lt;/a&gt; of your choice. Pick the latest version, if possible.&lt;/p&gt;

&lt;p&gt;You will spot two types of builds: Thread safe (a.k.a. TS or ZTS) and Non thread safe (a.k.a. NTS). Without going into details now, we will be using a setup where the NTS version should perform better so pick an NTS version.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;Note that with a "typical" Apache+PHP setup with &lt;code&gt;mod_php&lt;/code&gt;, like on XAMPP, you would have to pick the thread safe (ZTS) version to prevent concurrency issues.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unpack the contents in your install dir, be sure to create a subdir for each major PHP version, like &lt;code&gt;php84&lt;/code&gt;, &lt;code&gt;php82&lt;/code&gt;, &lt;code&gt;php74&lt;/code&gt; and so on. This is to ensure multiple versions may coexist alongside each other.&lt;br&gt;
Your path to PHP should look something like this &lt;code&gt;C:\dev\php\php84&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To configure PHP, locate the &lt;code&gt;php.ini&lt;/code&gt; file. If it is not present, look for &lt;code&gt;php.ini-development&lt;/code&gt;, make a copy and rename it to &lt;code&gt;php.ini&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I will leave the actual configuration up to you. But I do recommend tweaking values that do not suite you later on.&lt;/p&gt;

&lt;p&gt;You can test or verify the configuration by calling &lt;code&gt;php -v&lt;/code&gt; to see the version, &lt;code&gt;php -m&lt;/code&gt; to see the installed modules or &lt;code&gt;php -i&lt;/code&gt; to see all the configuration, which is verbose. Look out for any error/warning messages printed by PHP.&lt;/p&gt;

&lt;p&gt;To test the web interface, create a simple script &lt;code&gt;info.php&lt;/code&gt; and place the call to &lt;code&gt;phpinfo&lt;/code&gt; there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="nb"&gt;phpinfo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, visit it in the browser throught the particular domain (see vhosts below). You should see something like this:&lt;/p&gt;

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

&lt;h4&gt;
  
  
  HTTPS (curl, OpenSSL)
&lt;/h4&gt;

&lt;p&gt;To be able to run &lt;strong&gt;HTTPS&lt;/strong&gt; requests from PHP correctly, visit &lt;a href="https://curl.se/docs/caextract.html" rel="noopener noreferrer"&gt;CA certificates extracted from Mozilla&lt;/a&gt; site and download &lt;code&gt;cacert.pem&lt;/code&gt; file. Place it anywhere into your install dir.&lt;/p&gt;

&lt;p&gt;Be sure to include it in your &lt;code&gt;php.ini&lt;/code&gt; file (update the existing values):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[curl]
curl.cainfo = "C:/dev/cacert.pem"

[openssl]
openssl.cafile= "C:/dev/cacert.pem"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Xdebug
&lt;/h4&gt;

&lt;p&gt;Visit &lt;a href="https://xdebug.org/download" rel="noopener noreferrer"&gt;Xdebug downloads&lt;/a&gt; and pick appropriate version. Take care to select the right PHP version + x64 + NTS variant combination.&lt;/p&gt;

&lt;p&gt;Unpack the zip file - you will only need the &lt;code&gt;php_xdebug-3.4xxxxx.dll&lt;/code&gt; file. Place the dll into your php's &lt;code&gt;ext&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Include or modify the Xdebug section in your &lt;code&gt;php.ini&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;✋ Take great care here to specify the &lt;code&gt;zend_extension&lt;/code&gt; &lt;em&gt;without&lt;/em&gt; the &lt;code&gt;php_&lt;/code&gt; prefix and the &lt;code&gt;.dll&lt;/code&gt; suffix, otherwise the module will not load. The actual value here depends on the file name you place in the &lt;code&gt;ext&lt;/code&gt; dir.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Xdebug]
zend_extension=xdebug-3.4.1-8.4-nts-vs17-x86_64

; Debug enables step debugging, develop has better performance.
; https://xdebug.org/docs/all_settings#mode
xdebug.mode=debug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You may rename the file to just &lt;code&gt;php_xdebug.dll&lt;/code&gt; and use &lt;code&gt;zend_extension=xdebug&lt;/code&gt; in the ini file, but I recommend keeping the original file name. If you decide to rename it, you must keep the &lt;code&gt;php_&lt;/code&gt; prefix in the filename or the module will not load.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once configured correctly, typing &lt;code&gt;php -v&lt;/code&gt; in the terminal should validate correct configuration with Xdebug.&lt;br&gt;
If there are any issues, PHP will print them out.&lt;/p&gt;

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

&lt;p&gt;To update a PHP version, just download the new version and overwrite the installation folder contents. This will keep any extensions you added (like Xdebug) as well as the config file (&lt;code&gt;php.ini&lt;/code&gt;) intact.&lt;/p&gt;
&lt;h3&gt;
  
  
  Apache server
&lt;/h3&gt;

&lt;p&gt;Apache web server is software that listens for HTTP requests from clients (i.e. the browsers), processes these requests by invoking a PHP interpreter service and sends the resulting response back to the user.&lt;/p&gt;

&lt;p&gt;We will use module called &lt;strong&gt;FastCGI&lt;/strong&gt; (&lt;code&gt;mod_fcgid&lt;/code&gt;) with NTS PHP versions.&lt;/p&gt;

&lt;p&gt;Visit &lt;a href="https://www.apachelounge.com/download/" rel="noopener noreferrer"&gt;Apache Lounge download page&lt;/a&gt; and&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download Apache 2.4 x64&lt;/li&gt;
&lt;li&gt;also grab the FastCGI module (&lt;code&gt;mod_fcgid&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unpack the contents into your install dir, e.g. &lt;code&gt;C:\dev\Apache24&lt;/code&gt;, and place the &lt;code&gt;mod_fcgid&lt;/code&gt; in the &lt;code&gt;\modules&lt;/code&gt; subdir, e.g. &lt;code&gt;C:\dev\Apache24\modules&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Be sure to enable the module in &lt;code&gt;httpd.conf&lt;/code&gt; located in &lt;code&gt;C:\dev\Apache24\conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LoadModule fcgid_module modules/mod_fcgid.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I usually separate my per-project Apache configuration in a few config files located in the project dir, which can be included from &lt;code&gt;httpd.conf&lt;/code&gt; using a line like this (placed at the end I suppose):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IncludeOptional "R:\httpd-include.conf"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is my current DEV configuration, tweak it to whatever suits you, or do not include it at all to use the defaults. I used this configuration when experimenting with SSE (server-sent events), but then I completely forgot about it. It might be an overkill.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;IfModule fcgid_module&amp;gt;
    FcgidIOTimeout 600
    FcgidConnectTimeout 16
    FcgidBusyTimeout 600
    FcgidMaxRequestsPerProcess 500
    FcgidMaxProcesses 50
    # 4G = 4294967296
    FcgidMaxRequestLen 4294967296

    # for experiments with SSE / streaming
    FcgidOutputBufferSize 0
&amp;lt;/IfModule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apache will install as a service. You may want configure it to not start automatically and start it manually (hit Start button and type in "services"). It is possible to use "apache monitor" that comes bundled with the server. This app enables to start/stop and monitor Apache from taskbar.&lt;/p&gt;

&lt;p&gt;However, I'm using a small script to start/stop Apache, database and Garnet with a single command. More on this later.&lt;/p&gt;

&lt;p&gt;The configuration of Apache server can be tested calling apache with &lt;code&gt;-t&lt;/code&gt; parameter: &lt;code&gt;httpd.exe -t&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwuflitfhqgti65a6qnkt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwuflitfhqgti65a6qnkt.png" alt="apache config test" width="452" height="180"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Vhosts (virtual hosts)
&lt;/h3&gt;

&lt;p&gt;A "vhost" (abbreviation of "virtual host") is an isolated configuration listening for requests on a certain interface / domain.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I place the vhost configurations into the &lt;code&gt;R:\httpd-include.conf&lt;/code&gt; Apache config file included from the main config file. I'm using several such included files in fact. This is up to you to organize.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm using a separate vhost and domain for each project I'm working on for the flexibility, form prefill and other benefits.&lt;/p&gt;

&lt;p&gt;This is what a simple such configuration template may look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;
    ServerName your-project.lo
    DocumentRoot "R:/your-project"
    &amp;lt;directory "R:/your-project"&amp;gt;
        Options +ExecCGI +Indexes +FollowSymLinks
        AddHandler fcgid-script .php
        FCGIWrapper "C:/dev/php/php84/php-cgi.exe -c C:/dev/php/php84/php.ini" .php
        CGIPassAuth on
        AllowOverride all
        Require all granted
    &amp;lt;/directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 &lt;strong&gt;To swap PHP version, all that needs to change is the &lt;code&gt;FCGIWrapper&lt;/code&gt; directive.&lt;/strong&gt; 👈&lt;/p&gt;

&lt;p&gt;The vhost is set up once, though, there is no need to switch anything afterwards.&lt;/p&gt;

&lt;p&gt;Depending on your configuration, multiple vhosts with different PHP versions may point to the same directory (if switching git branches is the way you work when upgrading).&lt;/p&gt;

&lt;p&gt;I'm using &lt;code&gt;.lo&lt;/code&gt; as TLD, but I've seen &lt;code&gt;.loc&lt;/code&gt; and &lt;code&gt;.local&lt;/code&gt; too. You may experiment here.&lt;/p&gt;

&lt;p&gt;You may also define a server alias, like &lt;code&gt;www.your-project.lo&lt;/code&gt; adding&lt;br&gt;
&lt;code&gt;ServerAlias www.your-project.lo&lt;/code&gt; to the configuration.&lt;/p&gt;

&lt;p&gt;I'm using this as a template to create other vhosts, it is also possible to automate such creation, but it's not much of a hassle anyway.&lt;/p&gt;
&lt;h4&gt;
  
  
  Hosts file
&lt;/h4&gt;

&lt;p&gt;You may have already checked that visiting &lt;code&gt;your-project.lo&lt;/code&gt; in the browser probably sends you to your search engine.&lt;/p&gt;

&lt;p&gt;Your computer does not recognize the domain and looks for the IP address using DNS, which fails for &lt;code&gt;.lo&lt;/code&gt; TLD and you end up defaulting to search engine.&lt;/p&gt;

&lt;p&gt;To overcome this, edit the &lt;code&gt;hosts&lt;/code&gt; file in your system. This file is always checked before any DNS requests are issued.&lt;/p&gt;

&lt;p&gt;The file is located here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Windows\system32\drivers\etc\hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file can be used for other stuff, but here we will only direct our development domains to the localhost like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#
# My projects
#
  127.0.0.1      your-project.lo
  127.0.0.1      adminer.lo next.adminer.lo
#...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will tell the system to direct all network traffic going to &lt;code&gt;your-project.lo&lt;/code&gt;, &lt;code&gt;adminer.lo&lt;/code&gt; and &lt;code&gt;next.adminer.lo&lt;/code&gt; to your own localhost. Apache will take it from there.&lt;/p&gt;

&lt;p&gt;Save this file and you are ready to go. After visiting your project domain in the browser, you should see it being rendered.&lt;/p&gt;

&lt;p&gt;The file may be read-only or system-protected, so look for write errors when you save it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that to edit the hosts file you will probably need to open it with administrator privileges, if you get errors when saving the modifications of the file. alternatively, you may try the &lt;a href="https://learn.microsoft.com/en-us/windows/powertoys/" rel="noopener noreferrer"&gt;Microsoft PowerToys&lt;/a&gt; that come with a hostfile editor and a bunch of other more or less useful stuff.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Vhost with HTTPS example
&lt;/h4&gt;

&lt;p&gt;Here, I will set up domains &lt;code&gt;adminer.lo&lt;/code&gt; and &lt;code&gt;next.adminer.lo&lt;/code&gt; for local development with HTTPS redirect. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I believe that to use HTTPS you would have to generate your own self-signed certificate. I did this long time ago and have not documented how, so I'll leave this for you to experiment with. Or use your favourite AI for suggestions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the vhost conf for &lt;code&gt;adminer.lo&lt;/code&gt; which runs on legacy PHP 7.4:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;
    ServerName adminer.lo
    Redirect / https://adminer.lo/
&amp;lt;/VirtualHost&amp;gt;
&amp;lt;VirtualHost 127.0.0.1:443&amp;gt;
    ServerName adminer.lo
    DocumentRoot "R:/adminer"
    ErrorLog "R:/adminer/error.log"
    &amp;lt;directory "R:/adminer"&amp;gt;
        Options +ExecCGI +Indexes +FollowSymLinks
        AddHandler fcgid-script .php
        FCGIWrapper "C:/dev/php/php74/php-cgi.exe -c C:/dev/php/php74/php.ini" .php
        CGIPassAuth on
        AllowOverride all
        Require all granted
    &amp;lt;/directory&amp;gt;
    SSLEngine on
    SSLCertificateFile ${SRVROOT}/conf/server.crt
    SSLCertificateKeyFile ${SRVROOT}/conf/server.key
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is the vhost for running my updated Adminer fork:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;
    ServerName next.adminer.lo
    Redirect / https://next.adminer.lo/
&amp;lt;/VirtualHost&amp;gt;
&amp;lt;VirtualHost 127.0.0.1:443&amp;gt;
    ServerName next.adminer.lo
    DocumentRoot "R:/adminer-next"
    ErrorLog "R:/adminer-next/error.log"
    &amp;lt;directory "R:/adminer-next"&amp;gt;
        Options +ExecCGI +Indexes +FollowSymLinks
        AddHandler fcgid-script .php
        FCGIWrapper "C:/dev/php/php84/php-cgi.exe -c C:/dev/php/php84/php.ini" .php
        CGIPassAuth on
        AllowOverride all
        Require all granted
    &amp;lt;/directory&amp;gt;
    SSLEngine on
    SSLCertificateFile ${SRVROOT}/conf/server.crt
    SSLCertificateKeyFile ${SRVROOT}/conf/server.key
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever I visit &lt;code&gt;https://adminer.lo/&lt;/code&gt; in the browser, PHP 7.4 will serve this request;&lt;br&gt;
but whenever I visit &lt;code&gt;https://next.adminer.lo/&lt;/code&gt;, PHP 8.4 will serve that request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I do not need to switch anything&lt;/strong&gt; and I am able to work on both in parallel.&lt;/p&gt;
&lt;h4&gt;
  
  
  Dedicated document roots for different PHP versions
&lt;/h4&gt;

&lt;p&gt;Above, I'm showing how I configure a vhost/domain for each project.&lt;br&gt;
If you'd rather use a single vhost for a PHP version, that is also possible. You may choose to use domains like &lt;code&gt;php84.lo&lt;/code&gt; and &lt;code&gt;php82.lo&lt;/code&gt; or use different ports to direct traffic to the right vhost, and only create a single vhost for each PHP version.&lt;/p&gt;

&lt;p&gt;Your projects wil then be accessible via &lt;code&gt;https://php84.lo&lt;/code&gt; or &lt;code&gt;http://localhost:8084&lt;/code&gt;, this is up ot you to experiment with.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that &lt;code&gt;80&lt;/code&gt; is the default HTTP port and &lt;code&gt;443&lt;/code&gt; is the default HTTPS port, though &lt;code&gt;8080&lt;/code&gt; is commonly used for dev servers (e.g. Vue.js).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  MariaDB / MySQL
&lt;/h3&gt;

&lt;p&gt;It should not really matter whether you use MySQL or MariaDB in most cases. There are (minor) differences though, so pick the one that your clients use in production. Or install both.&lt;/p&gt;

&lt;p&gt;Visit the &lt;a href="https://mariadb.org/download" rel="noopener noreferrer"&gt;MariaDB download page&lt;/a&gt; and pick a version. I recommend a "long-term support" version. At time of writing, this would be &lt;code&gt;11.4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Install the database into &lt;code&gt;C:\dev\MariaDB&lt;/code&gt;. I also create a separate dir for the actual database data, e.g. &lt;code&gt;C:\dev\MariaDB_data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;my.ini&lt;/code&gt; located in the &lt;code&gt;MariaDB_data&lt;/code&gt; folder looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[mysqld]
datadir=C:/dev/local/MariaDB_data
port=3306
character-set-server=utf8mb4
innodb_buffer_pool_size=8G
max_heap_table_size=1G
[client]
port=3306
plugin-dir=C:/dev/MariaDB/lib/plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I prefer to setup &lt;code&gt;utf8mb4&lt;/code&gt; as the default charset and increase memory allowances for smooth operation. I'm not sure what the &lt;code&gt;[client]&lt;/code&gt; section is for though.&lt;/p&gt;

&lt;p&gt;For database management, I use &lt;a href="https://www.adminer.org/" rel="noopener noreferrer"&gt;Adminer&lt;/a&gt;, which is minimalist, but provides all the convinience I need. You may consider phpMyAdmin or HeidiSQL or something similar.&lt;/p&gt;

&lt;p&gt;If you need to install multiple versions, or both MySQL and MariaDB, adjust the ports so that each listens on a different port. This will have to be reflected in your application.&lt;/p&gt;

&lt;p&gt;As with Apache, you may want to validate the configuration calling mysqld with the &lt;code&gt;--validate-config&lt;/code&gt; parameter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with multiple PHP versions in parallel
&lt;/h2&gt;

&lt;p&gt;Now to the scripting part. There are no vhosts in CLI, so if you open your terminal (CMD, command line) and type in &lt;code&gt;php -v&lt;/code&gt; you will get whichever PHP you installed as default, if any.&lt;/p&gt;

&lt;p&gt;The trick is to create a directory (which I will call the "bin" directory) in &lt;code&gt;C:/dev/bin&lt;/code&gt; and include it in the PATH system variable. Here we will place our &lt;code&gt;.bat&lt;/code&gt; batch command files.&lt;/p&gt;

&lt;p&gt;Go to "Edit system environment variables" and edit the &lt;code&gt;PATH&lt;/code&gt; variable so that it includes this directory. Windows will pick the first binary in the order which the folders appear in the PATH variable, so place the &lt;code&gt;C:/dev/bin&lt;/code&gt; part appropriately.&lt;/p&gt;

&lt;p&gt;Start a new terminal and type &lt;code&gt;echo %PATH%&lt;/code&gt; ... it should be visible there (one must reopen the terminal after changing the PATH variable).&lt;/p&gt;

&lt;p&gt;In this bin directory, I create files like &lt;code&gt;php84.bat&lt;/code&gt; for each PHP version. I also create &lt;code&gt;com84.bat&lt;/code&gt; for composer running with PHP 8.4 and any other version. This way I am able to invoke PHP binaries selectively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;

&lt;span class="c"&gt;REM PHP 8.4&lt;/span&gt;

&lt;span class="c"&gt;REM   %* passes all the input arguments to the PHP call&lt;/span&gt;
&lt;span class="s2"&gt;"C:/dev/php/php84/php.exe"&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="c"&gt;REM    Composer running using PHP 8.4&lt;/span&gt;

&lt;span class="c"&gt;REM   %* passes all the input arguments to the PHP call&lt;/span&gt;
&lt;span class="kd"&gt;php84&lt;/span&gt; &lt;span class="s2"&gt;"C:\dev\composer\composer.phar"&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am then able to invoke &lt;code&gt;php84&lt;/code&gt; for PHP v8.4 and &lt;code&gt;com84&lt;/code&gt; for Composer running on PHP 8.4. I create such a shortcut for each version I install and have one PHP version as the default for &lt;code&gt;php&lt;/code&gt; and &lt;code&gt;com&lt;/code&gt;/&lt;code&gt;composer&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fot0omx8v7eiv5t4vghzx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fot0omx8v7eiv5t4vghzx.png" alt="composer with PHP 8.4" width="439" height="144"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Composer scripts
&lt;/h2&gt;

&lt;p&gt;Normally, if you specify a &lt;em&gt;script&lt;/em&gt; that runs PHP or other Composer command in &lt;code&gt;composer.json&lt;/code&gt; like the following, you will see the script is invoked using the default &lt;code&gt;php&lt;/code&gt;/&lt;code&gt;composer&lt;/code&gt; binary and not the one you run the command with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cache:warmup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"php bin/cache-warmup.php"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cache:purge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"php bin/cache-purge.php"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cache:test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"composer cache:warmup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"composer cache:purge"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the issue here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"php -v"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script should print out the correct php version, but it prints the default system php version every time, which is not what we intended:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzeob8n5hkh18s1cto66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzeob8n5hkh18s1cto66.png" alt="php version check fail" width="641" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The trick is to use &lt;code&gt;@php&lt;/code&gt; and &lt;code&gt;@composer&lt;/code&gt; in the JSON file, that tells composer to "use the same PHP binary as it was invoked by":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@php -v"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;As a rule of thumb, &lt;strong&gt;always specify your scripts as &lt;code&gt;@php&lt;/code&gt; and &lt;code&gt;@composer&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cache:warmup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@php bin/cache-warmup.php"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cache:purge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@php bin/cache-purge.php"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cache:test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@composer cache:warmup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@composer cache:purge"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Built-in server
&lt;/h2&gt;

&lt;p&gt;Assuming your project uses a &lt;code&gt;/public&lt;/code&gt; dir as the document root, you may also just run the built-in PHP server, but keep in mind that it is not intended for serving static files (images, CSS, JS).&lt;/p&gt;

&lt;p&gt;In the terminal, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php -S 127.0.0.1:42300 -t public/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm using this kind of &lt;code&gt;composer.json&lt;/code&gt; structure, then I call it as &lt;code&gt;composer serve&lt;/code&gt; in the project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Composer&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Config::disableProcessTimeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@php -S 127.0.0.1:42300 -t public/"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts-descriptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Run a lightweight PHP server"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Remember, you are in control of which PHP version you run the script with. Be it &lt;code&gt;com84 serve&lt;/code&gt; or &lt;code&gt;com82 serve&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other useful scripts
&lt;/h2&gt;

&lt;p&gt;A script to start Apache server (with PHP module), MariaDB database and Garnet for cache - &lt;code&gt;amp-on.bat&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;

&lt;span class="c"&gt;REM Start Apache, MariaDB and Garnet&lt;/span&gt;
&lt;span class="nb"&gt;sc&lt;/span&gt; &lt;span class="nb"&gt;start&lt;/span&gt; &lt;span class="kd"&gt;ApacheHTTPServer&lt;/span&gt;
&lt;span class="nb"&gt;sc&lt;/span&gt; &lt;span class="nb"&gt;start&lt;/span&gt; &lt;span class="kd"&gt;MariaDB&lt;/span&gt;
&lt;span class="nb"&gt;sc&lt;/span&gt; &lt;span class="nb"&gt;start&lt;/span&gt; &lt;span class="kd"&gt;GarnetServer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and &lt;code&gt;amp-off&lt;/code&gt; script containing &lt;code&gt;sc stop&lt;/code&gt; for stopping the services as well.&lt;/p&gt;

&lt;p&gt;Then a script for restarting Apache. This one is more advanced and waits for the service to stop and start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;

&lt;span class="c"&gt;REM REstart Apache&lt;/span&gt;
&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="s2"&gt;"ServiceName=ApacheHTTPServer"&lt;/span&gt;

&lt;span class="c"&gt;REM Stop the service&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="kd"&gt;Service&lt;/span&gt; &lt;span class="nv"&gt;%ServiceName%&lt;/span&gt; &lt;span class="kd"&gt;is&lt;/span&gt; &lt;span class="kd"&gt;shutting&lt;/span&gt; &lt;span class="kd"&gt;down&lt;/span&gt;.
&lt;span class="nb"&gt;sc&lt;/span&gt; &lt;span class="kd"&gt;stop&lt;/span&gt; &lt;span class="nv"&gt;%ServiceName%&lt;/span&gt;

&lt;span class="c"&gt;REM Wait for the service to stop&lt;/span&gt;
&lt;span class="nl"&gt;:LOOP&lt;/span&gt;
&lt;span class="nb"&gt;timeout&lt;/span&gt; &lt;span class="na"&gt;/t &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="na"&gt;/nobreak &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="kr"&gt;nul&lt;/span&gt;
&lt;span class="nb"&gt;sc&lt;/span&gt; &lt;span class="nb"&gt;query&lt;/span&gt; &lt;span class="nv"&gt;%ServiceName%&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;find&lt;/span&gt; &lt;span class="s2"&gt;"STATE"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;find&lt;/span&gt; &lt;span class="s2"&gt;"STOPPED"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="kr"&gt;nul&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;%errorlevel%&lt;/span&gt; &lt;span class="ow"&gt;equ&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="kd"&gt;START_SERVICE&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="kd"&gt;LOOP&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;:START&lt;/span&gt;_SERVICE
&lt;span class="c"&gt;REM Start the service&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;.
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="kd"&gt;Service&lt;/span&gt; &lt;span class="nv"&gt;%ServiceName%&lt;/span&gt; &lt;span class="kd"&gt;is&lt;/span&gt; &lt;span class="kd"&gt;starting&lt;/span&gt;.
&lt;span class="nb"&gt;sc&lt;/span&gt; &lt;span class="nb"&gt;start&lt;/span&gt; &lt;span class="nv"&gt;%ServiceName%&lt;/span&gt;

&lt;span class="c"&gt;REM Wait for the service to start&lt;/span&gt;
&lt;span class="nl"&gt;:WAIT&lt;/span&gt;_START
&lt;span class="nb"&gt;timeout&lt;/span&gt; &lt;span class="na"&gt;/t &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="na"&gt;/nobreak &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="kr"&gt;nul&lt;/span&gt;
&lt;span class="nb"&gt;sc&lt;/span&gt; &lt;span class="nb"&gt;query&lt;/span&gt; &lt;span class="nv"&gt;%ServiceName%&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;find&lt;/span&gt; &lt;span class="s2"&gt;"STATE"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;find&lt;/span&gt; &lt;span class="s2"&gt;"RUNNING"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="kr"&gt;nul&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;%errorlevel%&lt;/span&gt; &lt;span class="ow"&gt;equ&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="kd"&gt;SHOW_MESSAGE&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="kd"&gt;WAIT_START&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;:SHOW&lt;/span&gt;_MESSAGE
&lt;span class="nb"&gt;echo&lt;/span&gt;.
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="kd"&gt;Service&lt;/span&gt; &lt;span class="nv"&gt;%ServiceName%&lt;/span&gt; &lt;span class="kd"&gt;is&lt;/span&gt; &lt;span class="nb"&gt;now&lt;/span&gt; &lt;span class="kd"&gt;running&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;It is not a short setup, but it pays off in the long run. Installing further PHP versions is just about creating a couple of files and folders and copy-pasting the &lt;code&gt;php.ini&lt;/code&gt;, sometimes tweaking a value here and there. Updating is a breeze.&lt;/p&gt;

&lt;p&gt;You will benefit from efficient and flexible Windows setup.&lt;/p&gt;

&lt;p&gt;No more waiting for third party bundlers to realease a new version!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tooling</category>
      <category>php</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Tree traversal and merging with Generators</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Sun, 11 Feb 2024 16:01:32 +0000</pubDate>
      <link>https://dev.to/dakujem/tree-traversal-and-merging-with-generators-3ojm</link>
      <guid>https://dev.to/dakujem/tree-traversal-and-merging-with-generators-3ojm</guid>
      <description>&lt;h2&gt;
  
  
  Tree traversal
&lt;/h2&gt;

&lt;p&gt;Recently I reimplemented my old library for tree structure handling.&lt;/p&gt;

&lt;p&gt;It turns out that &lt;strong&gt;generators&lt;/strong&gt; along with &lt;strong&gt;recursion&lt;/strong&gt; are a very efficient and concise way to traverse tree structures.&lt;/p&gt;

&lt;p&gt;Below, the two depth-first traversals (pre-order, post-order) are implemented by 3 lines of code, and the breadth-first level-order traversal by 5.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Traversal&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;preOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;TreeNodeContract&lt;/span&gt; &lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Generator&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// First, yield the current node,&lt;/span&gt;
        &lt;span class="c1"&gt;// then do the same for all the children.&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;preOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$child&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;postOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;TreeNodeContract&lt;/span&gt; &lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Generator&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Yield the current node last,&lt;/span&gt;
        &lt;span class="c1"&gt;// after recursively calling this for all it's children.&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;postOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$child&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;levelOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;TreeNodeContract&lt;/span&gt; &lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Generator&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// In BFS traversal a queue has to be used instead of recursion.&lt;/span&gt;
        &lt;span class="nv"&gt;$queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="c1"&gt;// The first node in the queue is taken and yielded,&lt;/span&gt;
        &lt;span class="c1"&gt;// then all of its children are added to the queue.&lt;/span&gt;
        &lt;span class="c1"&gt;// The next iterations will continue with the node's children, while adding their children to the queue,&lt;/span&gt;
        &lt;span class="c1"&gt;// until there are no more nodes to traverse.&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_shift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$queue&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$queue&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$child&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;💡&lt;/p&gt;

&lt;p&gt;You can tell a generator by the &lt;code&gt;yield&lt;/code&gt; keyword. In fact, every method or function that &lt;code&gt;yield&lt;/code&gt; appears in automatically returns a generator. A generator is a special type of single-use iterator. In simple terms, it is best used with &lt;code&gt;foreach&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Wikipedia will tell you more about &lt;a href="https://en.wikipedia.org/wiki/Depth-first_search" rel="noopener noreferrer"&gt;DFS&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Breadth-first_search" rel="noopener noreferrer"&gt;BFS&lt;/a&gt; tree traversals.&lt;br&gt;
Try to compare the code complexity using stacks to implement the same.&lt;/p&gt;

&lt;p&gt;If you are interested in tree structures in PHP, see the library &lt;a href="https://github.com/dakujem/oliva" rel="noopener noreferrer"&gt;&lt;code&gt;dakujem/oliva&lt;/code&gt;&lt;/a&gt;, it may help you speed up your work.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/dakujem" rel="noopener noreferrer"&gt;
        dakujem
      &lt;/a&gt; / &lt;a href="https://github.com/dakujem/oliva" rel="noopener noreferrer"&gt;
        oliva
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Flexible tree structures, materialized path trees, tree iterators.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Generators for merging arrays and iterators
&lt;/h2&gt;

&lt;p&gt;In my opinion, &lt;strong&gt;generators&lt;/strong&gt; are one of the lesser known and underrated features of PHP, albeit with narrow field of use, but very useful in some specific cases.&lt;/p&gt;

&lt;p&gt;See how this simple function "chains" multiple &lt;code&gt;iterable&lt;/code&gt;s? That means any arrays or iterators in one bag.&lt;br&gt;
And it's &lt;strong&gt;efficient&lt;/strong&gt;, it does not actually merge anything, it merely enables iteration over every element of whatever the input is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;chained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;iterable&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Generator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$iterable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="nv"&gt;$iterable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From now on, if you need to iterate over multiple arrays, no need to use &lt;code&gt;array_merge&lt;/code&gt; (which would merge the arrays, increasing memory usage and possibly slowing down the code).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;chained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$array1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$array2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$array3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, take care that the keys may overlap - a single key may appear multiple times for different values and is thus not unique. That would be problematic if you used &lt;code&gt;iterator_to_array&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;chained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above results in the expected sequence of numbers &lt;code&gt;1 2 3 4 5&lt;/code&gt; being printed.&lt;br&gt;
However, the below code does not print what's naturally expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;iterator_to_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$numbers&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array
(
    [0] =&amp;gt; 4
    [1] =&amp;gt; 5
    [2] =&amp;gt; 3
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because the indexes in the input arrays overlap and are overwritten when calling &lt;code&gt;iterator_to_array&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This one works as expected again, as there are no overlapping indexes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;chained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;iterator_to_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explore &lt;a href="https://3v4l.org/DAIQV" rel="noopener noreferrer"&gt;this 3v4l&lt;/a&gt; to see the code in action.&lt;/p&gt;

&lt;p&gt;The bottomline is this:&lt;br&gt;
We do not use generators for chaining if all we need is to create a merged array, but we may benefit from generators when we need to efficiently iterate over chained arrays or other iterable collections.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By the way, I've written a bit about generators before in &lt;a href="https://dev.to/dakujem/anonymous-generator-in-php-4nn1"&gt;Anonymous Generator in PHP&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>php</category>
      <category>code</category>
    </item>
    <item>
      <title>Swiftmailer on PHP 8.2</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Tue, 06 Jun 2023 18:50:25 +0000</pubDate>
      <link>https://dev.to/dakujem/swiftmailer-on-php-82-4elk</link>
      <guid>https://dev.to/dakujem/swiftmailer-on-php-82-4elk</guid>
      <description>&lt;p&gt;One of the essential requirements for software to be maintained is the ability to upgrade the internals, hardware, operating system etc. to the latest possible versions. Long-term.&lt;/p&gt;

&lt;p&gt;Lately, I've been working on a few legacy apps that needed new life breathed into them. Some ran 7.4, some even older versions of PHP.&lt;/p&gt;

&lt;p&gt;One of external dependencies that these apps had in common was now long abandoned &lt;a href="https://swiftmailer.symfony.com/docs/introduction.html" rel="noopener noreferrer"&gt;Swiftmailer by Symfony&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The original Swiftmailer runs on PHP versions up to 8.1, and no later. PHP 8.1 will be &lt;a href="https://www.php.net/supported-versions.php" rel="noopener noreferrer"&gt;end-of-life&lt;/a&gt; by the end of 2024.&lt;/p&gt;

&lt;p&gt;For some reason I've had trouble finding a fork that would reliably support PHP 8.2.&lt;/p&gt;

&lt;p&gt;Without further ado, here is a Swiftmailer fork that can be &lt;strong&gt;run without any deprecation warnings on PHP 8.2&lt;/strong&gt;: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/viaaurea" rel="noopener noreferrer"&gt;
        viaaurea
      &lt;/a&gt; / &lt;a href="https://github.com/viaaurea/swiftmailer-legacy" rel="noopener noreferrer"&gt;
        swiftmailer-legacy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Comprehensive mailing tools for PHP with recent PHP support
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Swift Mailer Legacy&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This is a fork of legacy &lt;a href="https://github.com/swiftmailer/swiftmailer" rel="noopener noreferrer"&gt;swiftmailer/swiftmailer&lt;/a&gt;
updated to run on &lt;strong&gt;PHP 8.2&lt;/strong&gt; and possibly future versions
without any deprecation warnings or compatibility issues,
maintained by &lt;a href="https://www.viaaurea.cz" rel="nofollow noopener noreferrer"&gt;Via Aurea&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Installation:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;composer require viaaurea/swiftmailer-legacy -W
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Legacy port development notes &lt;a href="https://github.com/viaaurea/swiftmailer-legacyport.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Swift Mailer&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The original Swiftmailer stopped being maintained at the end of November 2021.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Please, move to &lt;a href="https://symfony.com/doc/current/mailer.html" rel="nofollow noopener noreferrer"&gt;Symfony Mailer&lt;/a&gt; at your earliest convenience.
&lt;a href="https://symfony.com/doc/current/mailer.html" rel="nofollow noopener noreferrer"&gt;Symfony Mailer&lt;/a&gt; is the next evolution of Swiftmailer.
It provides the same features with support for modern PHP code and support for third-party providers.&lt;/p&gt;
&lt;p&gt;Swift Mailer is a component based mailing solution for PHP.
It is released under the MIT license.&lt;/p&gt;
&lt;p&gt;Swift Mailer is highly object-oriented by design and lends itself
to use in complex web application with a great deal of flexibility.&lt;/p&gt;
&lt;p&gt;For full details on usage, read the &lt;a href="https://swiftmailer.symfony.com/docs/introduction.html" rel="nofollow noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/viaaurea/swiftmailer-legacy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;All anyone willing to upgrade to PHP 8.2 has to do is type&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

composer require viaaurea/swiftmailer-legacy -W


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will replace the original package &lt;strong&gt;without breaking requirements&lt;/strong&gt; by other packages that rely on Swiftmailer being installed.&lt;/p&gt;

&lt;p&gt;The fork is a fully compatible drop-in replacement of the original Swiftmailer with minimal code changes to support PHP 8.2.&lt;/p&gt;

</description>
      <category>php</category>
      <category>symfony</category>
      <category>opensource</category>
      <category>mail</category>
    </item>
    <item>
      <title>From Singleton to Facade</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Tue, 11 Oct 2022 08:23:22 +0000</pubDate>
      <link>https://dev.to/dakujem/from-singleton-to-facade-4agk</link>
      <guid>https://dev.to/dakujem/from-singleton-to-facade-4agk</guid>
      <description>&lt;p&gt;Some have heard that singletons are evil. They are. Don't use them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR And don't use facades either, if you don't have to.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Towards better tomorrow
&lt;/h2&gt;

&lt;p&gt;"But singletons are so easy to use!" 👴&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Singleton&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;static&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is also simple to retain the utility of singletons and still use dependency injection, to a degree.&lt;br&gt;
It's not perfect, but it's a nice compromise for the imperfect world.&lt;/p&gt;

&lt;p&gt;There is only one difference between a singleton and a facade, and it's the &lt;strong&gt;explicit binding&lt;/strong&gt; of the instance:&lt;br&gt;
An explicit binding call must occur prior to using a facade.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Facade&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LogicException&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;?self&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The binding is usually done during the bootstrap phase of an application, using the app's service container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bootstrap the service container somehow...&lt;/span&gt;
&lt;span class="nv"&gt;$container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$bootstrap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;container&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Then bind the facade&lt;/span&gt;
&lt;span class="nc"&gt;SomeService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SomeService&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now both the injection via the service container will work as well as using the facade directly.&lt;/p&gt;

&lt;p&gt;This way, a legacy app may be refactored so that the legacy code remains functional, but dependency injection is introduced to the newer parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  An example of using an interface
&lt;/h2&gt;

&lt;p&gt;When implementing a facade, interfaces should always be used for the instances being bound. This will allow swapping the implementations behind them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EventBus&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;?EventDispatcherInterface&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;fire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;EventInterface&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LogicException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'The facade has not been bound to an instance.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;?EventDispatcherInterface&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$force&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$force&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LogicException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure it during app's startup&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;EventBus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EventDispatcherInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, both DI and the facade can coexist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SomeEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$dispatcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$container&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EventDispatcherInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$dispatcher&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;EventBus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may ask why the &lt;code&gt;$force&lt;/code&gt; parameter of the &lt;code&gt;EventBus::bind&lt;/code&gt; method. For practical purposes: You will want to test your facades and to do so, rebind them.&lt;/p&gt;

&lt;p&gt;I also suggest throwing an explanatory exception when attempting to bind the facade that has already been bound, like this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LogicException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'The facade has already been bound.'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="s1"&gt;'This error might imply design flaws.'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;
    &lt;span class="s1"&gt;'Consider injecting an %s implementation instead of using a facade.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;EventDispatcherInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Are singletons the root of all evil?
&lt;/h2&gt;

&lt;p&gt;Dont' get the wrong idea. You should NOT use facades just to allow global access to services. You still want to &lt;strong&gt;employ dependency injection&lt;/strong&gt; and inversion of control (IoC) principles.&lt;/p&gt;

&lt;p&gt;Facades build upon the very same bad idea of global access that drives singletons.&lt;/p&gt;

&lt;p&gt;On the other hand, there are cases where they might be practical (e.g. logging).&lt;/p&gt;

&lt;p&gt;And then there are &lt;strong&gt;legacy apps&lt;/strong&gt; that come with singletons already in place.&lt;br&gt;
Or legacy apps from the dark age that use no means of dependency injection at all.&lt;/p&gt;

&lt;p&gt;Implementing facades and utilizing a DI container might be a great step forward in those situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is wrong with singletons then?
&lt;/h3&gt;

&lt;p&gt;Tight couplings. Well, all static calls and &lt;code&gt;new&lt;/code&gt; operator uses create tight couplings.&lt;/p&gt;

&lt;p&gt;Tightly coupled code is harder to extend and harder to maintain, because implementations that have gotten deprecated can not be swapped for new ones. Tightly coupled code is harder to test, because big parts of the code needs to be mocked.&lt;/p&gt;

&lt;p&gt;When working with a facade that binds to an instance through an interface, the implementation behind the facade is allowed to change without modifications to the calling code.&lt;br&gt;
This alleviates the problem somewhat.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do singletons even exist?
&lt;/h3&gt;

&lt;p&gt;The singleton pattern solved an even greater evil of the dark ages - global variables. Singletons at least are immutable, while global variables may change on a whim.&lt;br&gt;
Singletons are a part of programming history and as such deserve respect. There is no place for them in a modern code, however.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Facades are not the clean solution that makes us happy and comfortable at the same time. But they are a better option than singletons.&lt;br&gt;
Especially in legacy apps where they provide compatibility with existing code while also paving a path towards brighter future.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The pitfalls of using traits</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Mon, 10 Oct 2022 19:32:00 +0000</pubDate>
      <link>https://dev.to/dakujem/the-pitfalls-of-using-traits-5321</link>
      <guid>https://dev.to/dakujem/the-pitfalls-of-using-traits-5321</guid>
      <description>&lt;p&gt;When traits came to PHP, I'm sure many (myself included) thought they were a viable solution for code reuse. They offered ways to reduce code verbosity. They were easy to grasp. Suddenly we needed not to duplicate code across unrelated classes. 🤩 However, many (myself included) sobered after a hard lesson...&lt;/p&gt;

&lt;h2&gt;
  
  
  What are traits in PHP
&lt;/h2&gt;

&lt;p&gt;In simple terms, a &lt;em&gt;trait&lt;/em&gt; is a piece of code that gets copy-pasted into a class using it.&lt;br&gt;
You can read what the &lt;a href="https://www.php.net/manual/en/language.oop5.traits.php" rel="noopener noreferrer"&gt;PHP Manual&lt;/a&gt; says about traits if you are not familiar with them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Traits came to my rescue
&lt;/h2&gt;

&lt;p&gt;I had a bunch of classes that all needed the same: a &lt;code&gt;Logger&lt;/code&gt;, a &lt;code&gt;Connection&lt;/code&gt; and a &lt;code&gt;Serializer&lt;/code&gt; instances (there were some other, it was about 6-8 such services IIRC). The constructors were bloated, there were ~50 lines of code just to assign these services to private props through a constructor, same comments, same everything, in every one of these classes...&lt;/p&gt;

&lt;p&gt;With every query I would have to type &lt;code&gt;$this-&amp;gt;connection-&amp;gt;table('foo')-&amp;gt;select(...)&lt;/code&gt; and then &lt;code&gt;$this-&amp;gt;serializer-&amp;gt;serialize($result)&lt;/code&gt; and whenever something needed logging &lt;code&gt;$this-&amp;gt;logger-&amp;gt;log(stuff)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Thinking of introducing base classes, are we? But my target classes were not all related, it would not have solved the issue, and, worse, base classes stink.&lt;/p&gt;

&lt;p&gt;So I experimented with "bundles"... I would inject a single class to the constructors, but then needed to type this kind of code &lt;code&gt;$this-&amp;gt;bundle-&amp;gt;logger-&amp;gt;log(stuff)&lt;/code&gt; 🤦‍♂️.&lt;/p&gt;

&lt;p&gt;Instead, I discovered with traits I could type just &lt;code&gt;$this-&amp;gt;log(stuff)&lt;/code&gt;, &lt;code&gt;$this-&amp;gt;select(...)&lt;/code&gt; or &lt;code&gt;$this-&amp;gt;serialize($result)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It was easy to write a trait with those methods, like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;injectConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Connection&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There was some magic calling the setter automagically on startup.&lt;/p&gt;

&lt;p&gt;I could even combine multiple such traits to create shortcuts to several services.&lt;/p&gt;

&lt;p&gt;A perfect solution.&lt;br&gt;
Except...&lt;/p&gt;

&lt;h3&gt;
  
  
  Except...
&lt;/h3&gt;

&lt;p&gt;So... How do you test a trait? Well, you don't! You can't instantiate it, you test a class that uses the trait.&lt;br&gt;
How do you know a class is using a trait, so that you can test it? Well, you don't&lt;sup id="fnref1"&gt;1&lt;/sup&gt;!&lt;br&gt;
See the problem?&lt;/p&gt;

&lt;h3&gt;
  
  
  Except...
&lt;/h3&gt;

&lt;p&gt;What about dependencies? You create a fancy class with half a dozen traits and as many services that you need to inject, somehow, and you end up with boilerplate code nevertheless.&lt;br&gt;
Worse, you create hidden dependences. And wait for the moment you start combining traits into supertraits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Think twice before creating and/or using a trait. I dare say, in most cases, if you need a trait, something in the class design has to smell.&lt;/p&gt;

&lt;p&gt;Remember that using a trait is just copy-pasting code that could otherwise sit in a proper class.&lt;/p&gt;

&lt;p&gt;Use class &lt;em&gt;composition&lt;/em&gt; instead of traits. Use DI containers with autowiring capabilities to reduce boilerplate. Understand that typing a couple of characters extra is cheaper than dealing with tangled dependencies and trait conflicts.&lt;br&gt;
In fact, those "bundles" I mentioned earlier, if executed correctly&lt;sup id="fnref2"&gt;2&lt;/sup&gt;, are the way to go in many cases.&lt;/p&gt;

&lt;p&gt;Traits are a valid language construct, though. Do use them where applicable. Make a conscious choice and be content with it.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Unless you inspect the source or use reflection. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Yes, I mean interfaces. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>php</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Fullstack web development in the twenties</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Mon, 10 Oct 2022 18:50:45 +0000</pubDate>
      <link>https://dev.to/dakujem/fullstack-web-development-in-the-twenties-4995</link>
      <guid>https://dev.to/dakujem/fullstack-web-development-in-the-twenties-4995</guid>
      <description>&lt;p&gt;I am a fullstack web developer.&lt;/p&gt;

&lt;p&gt;Well, used to be. Back when I started, it was enough to know PHP reasonably, then a bit of HTML (v4), CSS (v2), and some MySQL. The complexities of HTTP were well hidden by the MVC framework I was using at the time (Nette), SVN was as easy as &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;add&lt;/code&gt;, and &lt;code&gt;commit&lt;/code&gt; and there was this shell script that &lt;del&gt;deployed&lt;/del&gt; copied files from the repo to the vhost dir. This was enough to be productive and make something that generated profit.&lt;/p&gt;

&lt;p&gt;You needed to decide whether to switch from the three column 768px layout to the trendy 1024px width two column layout. Fixed-width, obviously.&lt;br&gt;
To be on the cutting edge and provide "interactive pages"&lt;sup id="fnref1"&gt;1&lt;/sup&gt; you used jQuery, and partial page update utilising AJAX.&lt;/p&gt;

&lt;p&gt;Your greatest enemy was the IE 6.&lt;/p&gt;

&lt;p&gt;Nowdays? Well...&lt;/p&gt;

&lt;p&gt;Everyone &lt;del&gt;wants interactive pages&lt;/del&gt; &lt;em&gt;expects&lt;/em&gt; interactive web apps now.&lt;br&gt;
To provide, you need to grasp a JavaScript/ES interactive tech like React, Vue, Angular. Or all of them. You need state management, event handling, routing ... you are basically building an application with almost everything that desktop apps need in place. It's nothing like "web pages" 10 years ago. And the JS eco is crazy. You can code in several syntaxes, then transpile, preprocess, bundle, postprocess.&lt;/p&gt;

&lt;p&gt;Responsiveness. You need to understand a CSS framework like Tailwind, Bootstrap, Bulma, Material. Or all of them. Pick the proper one. They come with Scss or Post CSS to master too. Your apps should look good on 2 inch wide phone noodle-screens with holes in them and corners eaten by mice, 32" 4k desktops and 80" TV screens alike.&lt;/p&gt;

&lt;p&gt;As a fullstack developer, you need to understand search engine optimization and optimize page load speeds. You did need to optimize in the past, but it was relatively simple because most of the heavy code was server-side.&lt;/p&gt;

&lt;p&gt;Once you have a shiny single page app drafted, you need to teach it to talk to your backend. Here you deal with the complexities of HTTP protocol, RESTful APIs or the overestimated GraphQL. On both sides.&lt;/p&gt;

&lt;p&gt;Almost forgot about databases! While relational SQL ones have not changed so much in principle, you now have a ton of NoSQL flavors and you should at least understand when to use or not use one.&lt;/p&gt;

&lt;p&gt;Once you have written a piece of code, you need to understand Git. And GitHub, GitLab, BitBucket or whatever your teams use (merge requests, pull requests, you name it). Git is awesome. But it is fundamentally more complex too, being decentralized. It's not just the commands, it's branching strategies, rebasing, merging...&lt;/p&gt;

&lt;p&gt;And then you need to deploy the code. Everything is in the cloud&lt;sup id="fnref2"&gt;2&lt;/sup&gt;, you need specialized tools to deploy your work. No longer is FTP enough. CI/CD, Docker containers, lambdas, buckets. You need to fiddle with that too.&lt;br&gt;
You need to set many of these things up before you even showcase a working draft.&lt;br&gt;
They call it DevOops for some reason. I've had most experience with the Oops part so far.&lt;/p&gt;

&lt;p&gt;As a fullstack developer, you, my reader, you are &lt;em&gt;expected&lt;/em&gt; to master all of that.&lt;br&gt;
&lt;strong&gt;Sure&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;Who are the mythical fullstack developers these days anyway?&lt;/p&gt;

&lt;p&gt;I would estimate the number of tech and tools I regularly use for development has doubled over the last few years, and it only seems to be gaining momentum. I wonder what my dev routines will look like by the end of this decade.&lt;/p&gt;

&lt;p&gt;I'm not offering any solution here. I just wanted to put these thoughts on paper. So that I can laugh when I stumble on this article in 10 years or so.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;the term "UX" probably did not exist at the time ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;just somebody else's computers, really ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>rant</category>
    </item>
    <item>
      <title>php 8 fun</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Wed, 18 Aug 2021 11:25:14 +0000</pubDate>
      <link>https://dev.to/dakujem/php-8-fun-2gn3</link>
      <guid>https://dev.to/dakujem/php-8-fun-2gn3</guid>
      <description>&lt;p&gt;From all of the breaking changes introduced in PHP 8 I think the change to the comparison operator is the most intricate one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// To be `true` or not to be? That is the question.
'' == 0
'' === 0
'0' == 0
'💩' == 0
'0foo' == 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the difference of evaluation in PHP 7 and PHP 8 here:&lt;br&gt;
👉 &lt;a href="https://3v4l.org/farC0" rel="noopener noreferrer"&gt;https://3v4l.org/farC0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I just don't understand how it's possible that I have not encountered a bug because of it yet.&lt;/p&gt;

&lt;p&gt;Hopefully, no sensible person is using the equality &lt;code&gt;==&lt;/code&gt; operator.&lt;/p&gt;

</description>
      <category>php</category>
      <category>discuss</category>
    </item>
    <item>
      <title>i18n, l10n, g11n, a11y, W1F</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Fri, 25 Dec 2020 21:18:21 +0000</pubDate>
      <link>https://dev.to/dakujem/i18n-l10n-g11n-a11y-w1f-448i</link>
      <guid>https://dev.to/dakujem/i18n-l10n-g11n-a11y-w1f-448i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Seriously?&lt;/strong&gt; How does this make anything more legible? Are we trading keyboard hits for legibility? I &lt;em&gt;would&lt;/em&gt; understand i18n, as internationalization really is a difficult word to type. But how often do we type this, honestly? And the rest? Saving 8-9 key hits? W1F&lt;sup id="fnref1"&gt;1&lt;/sup&gt; is wrong with just typing the word?&lt;/p&gt;

&lt;p&gt;Instead, why don't we settle on actual words that are inclusive and easy(-ier) to type?&lt;/p&gt;

&lt;p&gt;I just wish that one day we are able to use&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;unity&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;harmony&lt;/li&gt;
&lt;li&gt;world&lt;/li&gt;
&lt;li&gt;inclusion&lt;/li&gt;
&lt;li&gt;everyone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... or something like that.&lt;/p&gt;

&lt;p&gt;It seems to me like a race to find a new &lt;code&gt;[a-z][1-9][0-9]?[a-z]&lt;/code&gt; match. Or is the higher the number the better?&lt;/p&gt;

&lt;p&gt;I5e&lt;sup id="fnref2"&gt;2&lt;/sup&gt; I wrote the w3e&lt;sup id="fnref3"&gt;3&lt;/sup&gt; p2t&lt;sup id="fnref4"&gt;4&lt;/sup&gt; like this. Who would r2d&lt;sup id="fnref5"&gt;5&lt;/sup&gt; it?&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;what the ferret ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;imagine ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;whole ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;post ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;read ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>discuss</category>
      <category>i18n</category>
    </item>
    <item>
      <title>Recursion in PHP</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Sun, 29 Nov 2020 13:59:10 +0000</pubDate>
      <link>https://dev.to/dakujem/recursion-in-php-1af</link>
      <guid>https://dev.to/dakujem/recursion-in-php-1af</guid>
      <description>&lt;h2&gt;
  
  
  Chapter 1: Tail Recursion
&lt;/h2&gt;

&lt;p&gt;If we want to understand tail recursion,&lt;br&gt;
we need to read Chapter 1: Tail Recursion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chapter 2: Head Recursion
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;PHP&lt;/code&gt; means &lt;code&gt;PHP: Hypertext Preprocessor&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>php</category>
      <category>recursion</category>
    </item>
    <item>
      <title>Anonymous Generator in PHP</title>
      <dc:creator>Andrej Rypo</dc:creator>
      <pubDate>Sat, 28 Nov 2020 16:37:26 +0000</pubDate>
      <link>https://dev.to/dakujem/anonymous-generator-in-php-4nn1</link>
      <guid>https://dev.to/dakujem/anonymous-generator-in-php-4nn1</guid>
      <description>&lt;p&gt;Recently, I wanted to create an anonymous generator. The good news is that it is possible, the syntax is not the most obvious one, though.&lt;br&gt;
You need to use a technique called &lt;strong&gt;"immediately invoked function expression"&lt;/strong&gt; mostly known from JavaScript of the jQuery era.&lt;/p&gt;

&lt;p&gt;Here it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$iterable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Generator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// yield any number of values&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the anonymous function itself is not a generator, it's just a callable and can not be iterated over&lt;/li&gt;
&lt;li&gt;the function is invoked immediately after it is created and must be enclosed in parentheses &lt;code&gt;(function(){ ... })()&lt;/code&gt; if we want to invoke it right away&lt;/li&gt;
&lt;li&gt;the invocation results in a Generator, which can be iterated over
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Generator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nv"&gt;$iterable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$func&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// this is when the generator is created&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$func&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;     &lt;span class="c1"&gt;// error, $foo is not iterable&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$iterable&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// iterates over the generator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generators have been in PHP since version &lt;code&gt;5.5&lt;/code&gt;. I have since used them rarely, but they are good for solving some edge cases, like &lt;em&gt;generating&lt;/em&gt; a sequence of unknown length.&lt;/p&gt;

&lt;p&gt;A generator can be used to replace code like the one below. Yeah, for two items it might be useless, but for multiple such extractors it does make sense. Especially when you know only the first extractor will be used in 99% of cases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getDefaultExtractors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$headerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$cookieName&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;iterable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;#&amp;lt;  code here  &amp;gt;#&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$extractors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code with &lt;code&gt;if&lt;/code&gt;s and array push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$extractors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headerName&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$extractors&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Make&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;headerExtractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headerName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cookieName&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$extractors&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Make&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;cookieExtractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cookieName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code with &lt;code&gt;array_filter&lt;/code&gt; and ternaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$extractors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nv"&gt;$headerName&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Make&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;headerExtractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headerName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;$cookieName&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;Make&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;cookieExtractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cookieName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code using a generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$extractors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$cookieName&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Generator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$headerName&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Make&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;headerExtractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headerName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nv"&gt;$cookieName&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Make&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;cookieExtractor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cookieName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})(),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key idea behind the generators is that the execution is actually paused on each &lt;code&gt;yield&lt;/code&gt; statement, until the next iteration is initiated. For cases where the iteration is interrupted before all of the generated items are consumed, this saves some computation resources.&lt;/p&gt;

</description>
      <category>php</category>
      <category>code</category>
    </item>
  </channel>
</rss>
