Jekyll2022-11-25T19:07:53+00:00http://dotcom.software/feed.xmlMike’s dev blogProgramming enthusiast, mostly PHP. Father of two.Floating Dangers in PHP2022-10-04T00:00:00+00:002022-10-04T00:00:00+00:00http://dotcom.software/dangers-of-floats-in-php<p>Are you working with currencies? Do you use floats? You have to be extremely careful. Check out why.</p>
<h2 id="floating-dangers-in-php-on-medium"><a href="https://medium.com/@dotcom.software/floating-dangers-in-php-c4a2220bd8dc">Floating Dangers in PHP</a> on Medium.</h2>How to correctly compare floats in PHP? What are the dangers of using floating numbers in PHP.Primitive obsession anti-pattern2022-08-21T00:00:00+00:002022-08-21T00:00:00+00:00http://dotcom.software/primitive-obsession-antipattern<p>The primitive obsession is a kind of a smell that indicates poor code quality. We consider boolean, string, int, and float as primitive types in PHP. They are the building blocks you cannot avoid but they can be arranged into meaningful domain objects instead…</p>
<h2 id="primitive-obsession-anti-pattern-on-medium"><a href="https://blog.devgenius.io/primitive-obsession-anti-pattern-83fb5dcd5cc2">Primitive obsession anti-pattern</a> on Medium.</h2>Improve your code by reducing this code smell from the heart of your applicationUnit testing Twig’s Lazy Extensions2020-06-15T00:00:00+00:002020-06-15T00:00:00+00:00http://dotcom.software/unit-testing-lazy-twig-extensions<p><a href="/efficient-twig-extensions/">Last time</a> we wrote a
<a href="https://symfony.com/doc/current/templating/twig_extension.html#creating-lazy-loaded-twig-extensions">lazy Twig extension</a>
to speed up Twig initialization time.</p>
<p>Today it’s time to test it. There’s an excellent helper class called <code class="language-plaintext highlighter-rouge">IntegrationTestCase</code> in the Twig package.
This class expects a directory containing Twig test cases called “fixtures”.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">final</span> <span class="kd">class</span> <span class="nc">CheckoutStepsExtensionTest</span> <span class="kd">extends</span> <span class="nc">IntegrationTestCase</span>
<span class="p">{</span>
<span class="cd">/**
* The directory with test cases
*/</span>
<span class="k">protected</span> <span class="k">function</span> <span class="n">getFixturesDir</span><span class="p">():</span> <span class="kt">string</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">__DIR__</span> <span class="mf">.</span> <span class="s1">'/Fixtures'</span><span class="p">;</span>
<span class="p">}</span>
<span class="cd">/**
* Unit tested extensions
*/</span>
<span class="k">protected</span> <span class="k">function</span> <span class="n">getExtensions</span><span class="p">():</span> <span class="kt">array</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">[</span><span class="k">new</span> <span class="nc">CheckoutStepsExtension</span><span class="p">()];</span>
<span class="p">}</span>
<span class="cd">/**
* Called functions expects an "Order" object.
* Lets create a helper function will will return an Order.
*/</span>
<span class="k">protected</span> <span class="k">function</span> <span class="n">getTwigFunctions</span><span class="p">():</span> <span class="kt">array</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">[</span>
<span class="k">new</span> <span class="nc">TwigFunction</span><span class="p">(</span><span class="s1">'get_test_order'</span><span class="p">,</span> <span class="k">function</span> <span class="p">():</span> <span class="kt">OrderInterface</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">Order</span><span class="p">();</span>
<span class="p">}),</span>
<span class="p">];</span>
<span class="p">}</span>
<span class="cd">/**
* Since the runtime depends on its dependencies when created
* we need to register custom runtime loader which will spawn called runtime.
* For the sake of simplicity, we will mock the actual helper.
*/</span>
<span class="k">protected</span> <span class="k">function</span> <span class="n">getRuntimeLoaders</span><span class="p">():</span> <span class="kt">array</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">[</span><span class="k">new</span> <span class="nc">FactoryRuntimeLoader</span><span class="p">([</span>
<span class="nc">CheckoutStepsHelperRuntime</span><span class="o">::</span><span class="n">class</span> <span class="o">=></span> <span class="k">function</span> <span class="p">():</span> <span class="kt">CheckoutStepsHelperRuntime</span> <span class="p">{</span>
<span class="nv">$helper</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">prophesize</span><span class="p">(</span><span class="nc">CheckoutStepsHelper</span><span class="o">::</span><span class="n">class</span><span class="p">);</span>
<span class="nv">$helper</span><span class="o">-></span><span class="nf">isShippingRequired</span><span class="p">(</span><span class="nc">Argument</span><span class="o">::</span><span class="nf">type</span><span class="p">(</span><span class="nc">Order</span><span class="o">::</span><span class="n">class</span><span class="p">))</span><span class="o">-></span><span class="nf">willReturn</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="nv">$helper</span><span class="o">-></span><span class="nf">isPaymentRequired</span><span class="p">(</span><span class="nc">Argument</span><span class="o">::</span><span class="nf">type</span><span class="p">(</span><span class="nc">Order</span><span class="o">::</span><span class="n">class</span><span class="p">))</span><span class="o">-></span><span class="nf">willReturn</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">CheckoutStepsHelperRuntime</span><span class="p">(</span><span class="nv">$helper</span><span class="o">-></span><span class="nf">reveal</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">])];</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The test is complete, lets create some tests inside the “fixtures” directory:</p>
<p>The <code class="language-plaintext highlighter-rouge">fixtures/functions/sylius_is_shipping_required.test</code> file:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--TEST--
"sylius_is_shipping_required" function
--TEMPLATE--
--DATA--
return []
--EXPECT--
yes
</code></pre></div></div>
<p>Finally the <code class="language-plaintext highlighter-rouge">fixtures/functions/sylius_is_payment_required.test</code> file:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--TEST--
"sylius_is_payment_required" function
--TEMPLATE--
--DATA--
return []
--EXPECT--
yes
</code></pre></div></div>
<p>Run the suite <code class="language-plaintext highlighter-rouge">vendor/bin/phpunit</code> and we should be GREEN OK!
We are yellow OK though since the test case runs some weird <code class="language-plaintext highlighter-rouge">testLegacyIntegration</code> test which gets skipped.</p>
<p>However if we were to change the expectation to “no” we would end up with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'no'
+'yes'
/var/www/html/vendor/twig/twig/src/Test/IntegrationTestCase.php:251
/var/www/html/vendor/twig/twig/src/Test/IntegrationTestCase.php:82
</code></pre></div></div>Last time we wrote a lazy Twig Extension. Today it's time to test it.Efficient Twig extensions using Lazy Extensions2020-03-07T00:00:00+00:002020-03-07T00:00:00+00:00http://dotcom.software/efficient-twig-extensions<p><a href="https://symfony.com/doc/current/templating/twig_extension.html#creating-lazy-loaded-twig-extensions">Lazy Twig extensions</a>
were introduced in Twig 1.35 / 2.4.4, albeit there’re still see so many Symfony apps that don’t use them.</p>
<p>One has to be aware that to create the Twig environment, every extension has to be instantiated.
Now multiply that by the number of extensions in your app, some of them with possibly large dependency tree.</p>
<p>For instance, take a look at <a href="https://sylius.com/">Sylius</a> – popular e-commerce platform based on Symfony
and its <code class="language-plaintext highlighter-rouge">CheckoutStepsExtension</code> – it depends on a helper, which depends on two services, both depending on composite
resolvers, which in turn load another set of their dependencies.</p>
<p>This extension has to be instantiated every single time Twig is used, even though it’s not needed.</p>
<p>So instead of:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">final</span> <span class="kd">class</span> <span class="nc">CheckoutStepsExtension</span> <span class="k">extends</span> <span class="err">\</span><span class="n">Twig_Extension</span>
<span class="p">{</span>
<span class="k">private</span> <span class="nv">$checkoutStepsHelper</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span><span class="kt">CheckoutStepsHelper</span> <span class="nv">$checkoutStepsHelper</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="n">checkoutStepsHelper</span> <span class="o">=</span> <span class="nv">$checkoutStepsHelper</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">getFunctions</span><span class="p">():</span> <span class="kt">array</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">[</span>
<span class="k">new</span> <span class="err">\</span><span class="nf">Twig_Function</span><span class="p">(</span><span class="s1">'sylius_is_shipping_required'</span><span class="p">,</span> <span class="p">[</span><span class="nv">$this</span><span class="o">-></span><span class="n">checkoutStepsHelper</span><span class="p">,</span> <span class="s1">'isShippingRequired'</span><span class="p">]),</span>
<span class="k">new</span> <span class="err">\</span><span class="nf">Twig_Function</span><span class="p">(</span><span class="s1">'sylius_is_payment_required'</span><span class="p">,</span> <span class="p">[</span><span class="nv">$this</span><span class="o">-></span><span class="n">checkoutStepsHelper</span><span class="p">,</span> <span class="s1">'isPaymentRequired'</span><span class="p">]),</span>
<span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>we could move dependencies from the extension to a Twig Runtime:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">final</span> <span class="kd">class</span> <span class="nc">CheckoutStepsExtension</span> <span class="k">extends</span> <span class="err">\</span><span class="n">Twig_Extension</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">getFunctions</span><span class="p">():</span> <span class="kt">array</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">[</span>
<span class="k">new</span> <span class="err">\</span><span class="nf">Twig_Function</span><span class="p">(</span><span class="s1">'sylius_is_shipping_required'</span><span class="p">,</span> <span class="p">[</span><span class="nc">CheckoutStepsHelperRuntime</span><span class="o">::</span><span class="n">class</span><span class="p">,</span> <span class="s1">'isShippingRequired'</span><span class="p">]),</span>
<span class="k">new</span> <span class="err">\</span><span class="nf">Twig_Function</span><span class="p">(</span><span class="s1">'sylius_is_payment_required'</span><span class="p">,</span> <span class="p">[</span><span class="nc">CheckoutStepsHelperRuntime</span><span class="o">::</span><span class="n">class</span><span class="p">,</span> <span class="s1">'isPaymentRequired'</span><span class="p">]),</span>
<span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>and the Runtime proxy:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">final</span> <span class="kd">class</span> <span class="nc">CheckoutStepsHelperRuntime</span> <span class="kd">implements</span> <span class="nc">RuntimeExtensionInterface</span>
<span class="p">{</span>
<span class="k">private</span> <span class="nv">$checkoutStepsHelper</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span><span class="kt">CheckoutStepsHelper</span> <span class="nv">$checkoutStepsHelper</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="n">checkoutStepsHelper</span> <span class="o">=</span> <span class="nv">$checkoutStepsHelper</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">isShippingRequired</span><span class="p">(</span><span class="kt">OrderInterface</span> <span class="nv">$order</span><span class="p">):</span> <span class="kt">bool</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="n">checkoutStepsHelper</span><span class="o">-></span><span class="nf">isShippingRequired</span><span class="p">(</span><span class="nv">$order</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">isPaymentRequired</span><span class="p">(</span><span class="kt">OrderInterface</span> <span class="nv">$order</span><span class="p">):</span> <span class="kt">bool</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="n">checkoutStepsHelper</span><span class="o">-></span><span class="nf">isPaymentRequired</span><span class="p">(</span><span class="nv">$order</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">RuntimeExtensionInterface</code> interface is just for convenience. It enables Symfony DI’s autoconfiguration feature
to tag the class with the <code class="language-plaintext highlighter-rouge">twig.runtime</code> tag.</p>
<p>Last, but not least I hope, remember to test your Twig extensions. In the <a href="/unit-testing-lazy-twig-extensions/">next post</a>
I’ll show you how to do that properly.</p>Lazy Twig extensions were introduced in Twig 1.35 / 2.4.4, albeit there’re still see so many Symfony apps that don’t use them.How to enable Twig template autocompletion with Symfony integration plugin in phpStorm2018-11-01T00:00:00+00:002018-11-01T00:00:00+00:00http://dotcom.software/phpstorm-symfony-plugin-twig-template-autocompletion<p>The plugin currently is lacking support for the new Symfony’s directory structure
since we keep Twig files in the <code class="language-plaintext highlighter-rouge">templates/</code> directory.</p>
<p>The workaround is very simple, one just has to add the missing directory to the list of known Twig paths:</p>
<p><img src="/images/2018-11-01-phpstorm-symfony-plugin-twig-template-autocompletion.jpg" alt="Adding Twig path" /></p>
<p>That’s all, enjoy:</p>
<p><img src="/images/posts/2018-11-01-phpstorm-symfony-plugin-twig-template-autocompletion-2.jpg" alt="Adding Twig path" /></p>The plugin currently is lacking support for the new Symfony’s directory structure since we keep Twig files in the templates/ directory.How to configure Symfony 4 Doctrine XML mapping2018-10-24T00:00:00+00:002018-10-24T00:00:00+00:00http://dotcom.software/doctrine-xml-configuration<p>I’ve attempted to answer this question lately on <a href="https://stackoverflow.com/questions/52962708/symfony-4-how-to-implement-doctrine-xml-orm-mapping/52963004#52963004">StackOverflow</a>. Astoundingly, the documentation is scarce about this.</p>
<p>Imagine <code class="language-plaintext highlighter-rouge">YourDomain\Customer\Customer</code> domain object:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?php declare(strict_types=1);
namespace YourDomain\Customer;
class Customer
{
private $id;
private $email;
private $password;
public function __construct(string $email)
{
$this->setEmail($email);
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): void
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Not a valid e-mail address');
}
$this->email = $email;
}
public function getPassword(): string
{
return (string)$this->password;
}
public function setPassword(string $password): void
{
$this->password = $password;
}
}
</code></pre></div></div>
<p>Define your own mapping first:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>orm:
mappings:
YourDomain\Customer:
is_bundle: false
type: xml
// this is the location where xml files are located, mutatis mutandis
dir: '%kernel.project_dir%/../src/Infrastructure/ORM/Mapping'
prefix: 'YourDomain\Customer'
alias: Customer
</code></pre></div></div>
<p>File name has to match the pattern <code class="language-plaintext highlighter-rouge">[class_name].orm.xml</code>, in your case <code class="language-plaintext highlighter-rouge">Customer.orm.xml</code>. If you have sub-namespaces inside, eg. value object <code class="language-plaintext highlighter-rouge">YourDomain\Customer\ValueObject\Email</code>, the file has to be named <code class="language-plaintext highlighter-rouge">ValueObject.Email.orm.xml</code>.</p>
<p>Example mapping:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
<entity name="YourDomain\Customer\Customer" table="customer">
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name="email" type="email" unique="true"/>
<field name="password" length="72"/>
</entity>
</doctrine-mapping>
</code></pre></div></div>I’ve attempted to answer this question lately on StackOverflow. Astoundingly, the documentation is scarce about this.Conditional Xdebug breakpoints in phpStorm2018-07-06T00:00:00+00:002018-07-06T00:00:00+00:00http://dotcom.software/conditional-xdebug-breakpoints-in-phpstorm<p>How many times did you modify that big loop of yours just to make phpStorm enter a debugging session in the right place at the right time?</p>
<p>Well, that times are over now, thanks to phpStorm and its <a href="https://www.jetbrains.com/help/phpstorm/configuring-breakpoints.html">conditional breakpoints</a>! Why didn’t I know that fancy functionality exists? I feel really pity for myself.</p>
<p>Just add a breakpoint, right click on it and add your condition. And yes, those are pure PHP conditions, go bananas!</p>
<p><img src="/images/2018-07-06-conditional-debugger-breakpoints-in-phpstorm.md.png" alt="Creating a conditional breakpoint" /></p>
<p>Run your code and let the magic happen.</p>How many times did you modify that big loop of yours just to make phpStorm enter a debugging session in the right place at the right time?Generate UUID v6 in PHP2018-05-16T00:00:00+00:002018-05-16T00:00:00+00:00http://dotcom.software/generate-uuid-v6-with-php<p>The <a href="https://tools.ietf.org/html/rfc4122">RFC 4122</a> describes five versions of UUID,
but none of them is optimized to be used as a primary key in a relational database
we are so fond of.</p>
<p>Brad Peabody <a href="https://bradleypeabody.github.io/uuidv6/">proposed Version 6</a>:</p>
<blockquote>
<p>TL;DR: ‘Version 6’ UUIDs have the date/time encoded from high to low bytes
(bit-shifting around the version field in order to preserve its location)
and thus sort correctly by time when treated as an opaque bunch of bytes;
the clock sequence can be used to avoid duplicates generated at the same time;
it’s okay to use random data from a good PRNG in place of the MAC address;
the rest is the same as RFC 4122.</p>
</blockquote>
<p>Pity I didn’t found any PHP implementation, needed to write one myself.
Go and grab it from Packagist as <a href="https://packagist.org/packages/mikemix/php-uuid-v6">mikemix/php-uuid-v6</a>.
Hope it helps someone!</p>
<p>Hopefully this proposal will make its way through the IETF process some day!
<a href="https://github.com/ramsey">Ben Ramsey</a> already <a href="https://github.com/ramsey/uuid/issues/228">pledged his help</a>.</p>The RFC 4122 describes five versions of UUID, but none of them is optimized to be used as a primary key in a relational database we are so fond of.Dockerized MySQL hot backup tool with Percona Xtrabackup2018-01-19T00:00:00+00:002018-01-19T00:00:00+00:00http://dotcom.software/dockerized-mysql-hot-backup-with-percona-xtrabackup<p>Just created a dockerized tool that wraps Percona Xtrabackup binary and
periodically does a hot backup of your MySQL/MariaDB database. I already use it on production
and it works like a charm! Feel free to use and/or contribute. Licensed under MIT so anyone can use.</p>
<p><a href="https://hub.docker.com/r/mikemix/percona-xtrabackup/">Docker image</a> /
<a href="https://github.com/mikemix/percona-xtrabackup-cron">GitHub repository</a></p>
<p>Just mount your cloud bucket to the filesystem, one docker command to run the tool
and don’t worry about your database anymore.</p>Just created a dockerized tool that wraps Percona Xtrabackup binary and periodically does a hot backup of your MySQL/MariaDB database. I already use it on production and it works like a charm! Feel free to use and/or contribute. Licensed under MIT so anyone can use.Conway’s Game of Life implementation in PHP2017-08-29T00:00:00+00:002017-08-29T00:00:00+00:00http://dotcom.software/programming-can-be-fun<p>It’s really enjoyable sometimes to lay your daily activities and do anything fun topic. I managed to find some spare time recently so I got my hands on <a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life">Conway’s Game of Life</a> topic and did a simple implementation in PHP7.</p>
<p>It’s a simple algorithm, just 3 rules honestly, developed by <a href="https://en.wikipedia.org/wiki/John_Horton_Conway">John Conway</a> almost 40 years ago now. I’m not going to cover the topic thoroughly here though.</p>
<p>The results can be found at <a href="https://github.com/mikemix/game-of-life">GitHub</a>. Should you have Docker installed, the whole can be run with just one command.</p>
<p>I’m going to develop this from time to time for sure. I really hope that :)</p>It’s really enjoyable sometimes to lay your daily activities and do anything fun topic. I managed to find some spare time recently so I got my hands on Conway’s Game of Life topic and did a simple implementation in PHP7.