<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Jérémy DECOOL (@jdecool), Ingénieur Etudes et Développement à Lyon</title>
        <description></description>
        <link>https://www.jdecool.fr</link>
        <atom:link href="https://www.jdecool.fr/en/feed.xml" rel="self" type="application/rss+xml" />
        
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Push once, publish everywhere: the multi-target Git remote</title>
                    <description>&lt;p&gt;Github remains the go-to forge for hosting Git projects today. But as a French guy who lives in Europe, I don’t want to depend on only one foreign platform. So, to reduce that dependency, I decided to mirror all of my repositories (public and private) on &lt;a href=&quot;https://codeberg.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Codeberg&lt;/a&gt;, a European, non-profit forge.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Usually, people think a Git remote is a simple association with a URL (one remote = one destination). In reality, a remote is two things: a URL for fetching data and another one for pushing data. Using the &lt;code&gt;git remote -v&lt;/code&gt; command, it’s possible to visualize a repository configuration:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ git remote -v
origin  https://github.com/jdecool/repo.git (fetch)
origin  https://github.com/jdecool/repo.git (push)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Git allows to configure multiple push URLs to a remote. It’s what I do to push repository changes to Github and the Codeberg mirror:&lt;/p&gt;

&lt;p&gt;To declare the push URLs, use the &lt;code&gt;git remote set-url&lt;/code&gt; command with the &lt;code&gt;--add&lt;/code&gt; and &lt;code&gt;--push&lt;/code&gt; options:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ git remote set-url --add --push origin git@github.com:jdecool/repo.git
$ git remote set-url --add --push origin ssh://git@codeberg.org/jdecool/repo.git&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;It produces the following result:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$ git remote -v
origin  https://github.com/jdecool/repo.git (fetch)
origin  https://github.com/jdecool/repo.git (push)
origin  ssh://git@codeberg.org/jdecool/repo.git (push)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now the &lt;code&gt;origin&lt;/code&gt; remote still fetches data from Github, but it also pushes data on two destinations. The Git configuration is stored in the &lt;code&gt;.git/config&lt;/code&gt; file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-init&quot; data-lang=&quot;init&quot;&gt;[remote &amp;quot;origin&amp;quot;]
    url = git@github.com:jdecool/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*
    pushurl = git@github.com:jdecool/repo.git
    pushurl = ssh://git@codeberg.org/jdecool/repo.git&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, when I push changes with &lt;code&gt;git push origin main&lt;/code&gt;, Git will send commits to both Github and Codeberg. Both repository instances stay up to date.&lt;/p&gt;

&lt;p&gt;But there are some important things to notice.&lt;/p&gt;

&lt;p&gt;Fist, by default, Git uses the &lt;code&gt;fetch&lt;/code&gt; URL for pushing as well. But after you add a first &lt;code&gt;pushurl&lt;/code&gt;, the implicit &lt;code&gt;fetch&lt;/code&gt; URL is no longer used, it will be replaced. So &lt;strong&gt;make sure to declare the original URL&lt;/strong&gt; alongside the new one.&lt;/p&gt;

&lt;p&gt;Secondly, this technique remains a &lt;strong&gt;one-way mirror&lt;/strong&gt;. The fetch URL still contains only one URL. If some code is directly pushed to a secondly instance, those changes won’t be pulled back automatically. In a team context, prefer using the Git mirror feature from the forge server-side.&lt;/p&gt;
</description>
                    <pubDate>Tue, 14 Jul 2026 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2026/07/14/push-once-publish-everywhere-the-multi-target-git-remote.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2026/07/14/push-once-publish-everywhere-the-multi-target-git-remote.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Profile your PHPUnit tests using OpenTelemetry</title>
                    <description>&lt;p&gt;When your project grows, the codebase grows and your test suite too. The more the test you write, the longer the execution is. And then, the &lt;code&gt;Allowed memory size exhausted&lt;/code&gt; error occurred in your CI. To solve this issue quickly, you bump PHP allocated memory. But it doesn’t fix the problem, and this cycle goes on for a while. Until you hit critical thresholds.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Fixing memory issues is really difficult, and there are not many tools to help. We need to retrieve information about our test executions. Monitoring a production application is a known good practice. So why this is not applied to our test code?&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://opentelemetry.io &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;OpenTelemetry&lt;/a&gt; emerged those years. It could be useful to use it in our context to produce traces and metrics about our test suite.&lt;/p&gt;

&lt;p&gt;I’ve recently discovered the &lt;a href=&quot;https://packagist.org/packages/flow-php/phpunit-telemetry-bridge &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;flow-php/phpunit-telemetry-bridge&lt;/a&gt; library. It brings observability to our PHPUnit tests using OpenTelemetry. The library consists of a PHPUnit extension that collects some data and sends them to any OTLP-compatible backend.&lt;/p&gt;

&lt;p&gt;Start using it by installing the component using Composer:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;composer require --dev flow-php/phpunit-telemetry-bridge&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then add the telemetry configuration into PHPUnit:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;phpunit&amp;gt;
    &amp;lt;!-- ... --&amp;gt;
    &amp;lt;extensions&amp;gt;
        &amp;lt;bootstrap class=&amp;quot;Flow\Bridge\PHPUnit\Telemetry\TelemetryExtension&amp;quot;&amp;gt;
            &amp;lt;parameter name=&amp;quot;service_name&amp;quot; value=&amp;quot;phpunit-opentelemetry&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;transport&amp;quot; value=&amp;quot;curl&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;endpoint&amp;quot; value=&amp;quot;http://localhost:4318&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;emit_traces&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;emit_metrics&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;emit_test_spans&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;emit_test_case_spans&amp;quot; value=&amp;quot;true&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;curl_connect_timeout_ms&amp;quot; value=&amp;quot;1000&amp;quot;/&amp;gt;
            &amp;lt;parameter name=&amp;quot;curl_timeout_ms&amp;quot; value=&amp;quot;2000&amp;quot;/&amp;gt;
        &amp;lt;/bootstrap&amp;gt;
    &amp;lt;/extensions&amp;gt;
&amp;lt;/phpunit&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After that, every time your tests are run, the library will send the telemetry backend. Then you can create some dashboards to visualize the data:&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/img/blog/20260601-profilez-vos-tests-phpunit-avec-opentelemetry/phpunit-otel-overview.png&quot; alt=&quot;Overview of a PHPUnit test suite&apos;s telemetry in a dashboard&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;Data contains execution details like duration, memory consumption, executed tests, etc.&lt;/p&gt;

&lt;center&gt;
    &lt;img src=&quot;/img/blog/20260601-profilez-vos-tests-phpunit-avec-opentelemetry/phpunit-otel-memory.png&quot; alt=&quot;Memory consumption of PHPUnit tests visualized through OpenTelemetry&quot; /&gt;
&lt;/center&gt;

&lt;p&gt;When using observability in your stack, don’t forget that instrumenting every test can be expensive. Use it when you really need it.&lt;/p&gt;

&lt;p&gt;Want to get more information? Check out &lt;a href=&quot;https://flow-php.com/documentation/components/bridges/phpunit-telemetry-bridge/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;the PHPUnit Telemetry bridge documentation&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Mon, 01 Jun 2026 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2026/06/01/profile-your-phpunit-tests-with-opentelemetry.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2026/06/01/profile-your-phpunit-tests-with-opentelemetry.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Using PHP with Airflow</title>
                    <description>&lt;p&gt;I’m currently working on a BI project where scripts are written in Python and orchestrated by &lt;a href=&quot;https://airflow.apache.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Apache Airflow&lt;/a&gt;. Airflow was originally designed and built for the Python ecosystem. But as a PHP developer, I want to use Airflow to orchestrate PHP stuff.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Airflow tasks (called DAG for &lt;em&gt;Directed Acyclic Graph&lt;/em&gt;) are executed through operators. One of them is the &lt;code&gt;BashOperator&lt;/code&gt; which allows to run a command directly on an Airflow worker. So if PHP is installed on it, a script can be executed.&lt;/p&gt;

&lt;p&gt;Let’s take this simple PHP script as an example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php

echo &amp;quot;Hello from PHP script!\n&amp;quot;;
echo &amp;quot;Execution time: &amp;quot; . date(&amp;#39;Y-m-d H:i:s&amp;#39;) . &amp;quot;\n&amp;quot;;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To create a DAG, we need to write some Pyhton code (it’s the only moment where Python is needed):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;from airflow import DAG
from airflow.providers.standard.operators.bash import BashOperator
from datetime import datetime

with DAG(
    dag_id=&amp;quot;run_php_script&amp;quot;,
    description=&amp;quot;A DAG that runs a PHP script&amp;quot;,
    schedule=None,
    start_date=datetime(2024, 1, 1),
    catchup=False,
    tags=[&amp;quot;php&amp;quot;],
) as dag:

    run_php_script = BashOperator(
        task_id=&amp;quot;run_php_script&amp;quot;,
        bash_command=&amp;quot;php /path/to/scripts/hello.php&amp;quot;,
    )&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The previous code triggers a PHP script into Airflow. Standard output and error are automatically captured in logs.&lt;/p&gt;

&lt;p&gt;If you want to try it yourself, &lt;a href=&quot;https://github.com/jdecool/airflow-php-demo &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;this repository&lt;/a&gt; has everything you need to spin up an Airflow environment ready to go.&lt;/p&gt;
</description>
                    <pubDate>Mon, 18 May 2026 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2026/05/18/using-php-with-airflow.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2026/05/18/using-php-with-airflow.html</guid>
                </item>
            
        
            
        
            
        
            
                <item>
                    <title>Execute CTE queries using Doctrine ORM</title>
                    <description>&lt;p&gt;There are a lot of developers who manipulate databases using an ORM and don’t know more SQL than the “classic” &lt;code&gt;SELECT ... FROM ... WHERE ...&lt;/code&gt; queries. But databases have various unknown features that can avoid programing data processing. One of them is CTE (for &lt;em&gt;Common Table Expressions&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;This article describes how to use this feature with PHP Doctrine ORM.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;First, let me explain what a CTE is. A Common Table Expression (CTE) is a named temporary result set that you define at the beginning of a SQL query using the &lt;code&gt;WITH&lt;/code&gt; clause. It exists only for the duration of that query and can be referenced like a table within it.&lt;/p&gt;

&lt;p&gt;To illustrate how it works, imagine a blog where articles can be stored and attached to one category. Categories are organized in a tree structure. Now, we want to retrieve a specific category with all its parents. The naïve PHP implementation can be something like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// src/Repository/CategoryRepository.php
class CategoryRepository extends ServiceEntityRepository
{
    // ...

    /**
     * @return list&amp;lt;Category&amp;gt;
     */
    function getCatagoriesWithParents(int $categoryId): array
    {
        $category = $this-&amp;gt;categoryRepository-&amp;gt;find($categoryId);

        $categories = [
            $category,
        ];

        while ($parent = $category-&amp;gt;getParent()) {
            $categories[] = $parent;
        }

        return $categories;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With the previous version, Doctrine will execute one query per loop: it’s the common N+1 problem. To resolve this issue through an SQL query, we can’t use a basic query. We need a recursive CTE. This SQL query can look like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;WITH RECURSIVE cte_category AS (
    -- starting point
    SELECT id, label, parent_id, 0 AS depth
    FROM category
    WHERE id = :id

    UNION ALL

    -- recursivity
    SELECT c.id, c.label, c.parent_id, cp.depth + 1
    FROM category c
    INNER JOIN cte_category cp ON c.id = cp.parent_id
)
SELECT id, label, parent_id FROM cte_category
ORDER BY depth DESC&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;First, the previous query retrieves the &lt;code&gt;:id&lt;/code&gt; category. Then, recursively, it retrieves all the parents. The &lt;code&gt;depth&lt;/code&gt; column is used to order the final result.&lt;/p&gt;

&lt;p&gt;The problem with this kind of SQL query is that Doctrine is not able to execute them natively. We can’t use the &lt;code&gt;QueryBuilder&lt;/code&gt; or a &lt;code&gt;DQL&lt;/code&gt; (&lt;em&gt;Doctrine Query Language&lt;/em&gt;) query. We need to execute a native query and map the result on an object.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;// src/Repository/CategoryRepository.php
class CategoryRepository extends ServiceEntityRepository
{
    // ...

    public function findAllWithParents(int $categoryId)
    {
        $rsm = new ResultSetMappingBuilder($this-&amp;gt;getEntityManager());
        $rsm-&amp;gt;addRootEntityFromClassMetadata(Category::class, &amp;#39;c&amp;#39;);

        $sql = &amp;lt;&amp;lt;&amp;lt;SQL
            WITH RECURSIVE with_categories AS (
                SELECT id, label, parent_id, 0 AS depth
                FROM category
                WHERE id = :id

                UNION ALL

                SELECT c.id, c.label, c.parent_id, cp.depth + 1
                FROM category c
                INNER JOIN with_categories cp ON c.id = cp.parent_id
            )
            SELECT id, label, parent_id FROM with_categories
            ORDER BY depth DESC
        SQL;

        return $this-&amp;gt;getEntityManager()
            -&amp;gt;createNativeQuery($sql, $rsm)
            -&amp;gt;setParameter(&amp;#39;id&amp;#39;, $categoryId)
            -&amp;gt;getResult();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In the previous code, the &lt;code&gt;ResultSetMappingBuilder&lt;/code&gt; maps the query results to the &lt;code&gt;Category&lt;/code&gt; entity object properties. The output will be a list of &lt;code&gt;Category&lt;/code&gt; ordered by ancestors.&lt;/p&gt;

&lt;p&gt;This kind of query allows a lot of things. For example, we can retrieve every post of a specific category, including the category parents:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;WITH RECURSIVE category_path AS (
    SELECT id, label, parent_id
    FROM category
    WHERE id = :id

    UNION ALL

    SELECT c.id, c.label, c.parent_id
    FROM category c
    INNER JOIN category_path cp ON c.id = cp.parent_id
)
SELECT a.id, a.title, a.category_id
FROM article a
INNER JOIN category_path cp ON a.category_id = cp.id
ORDER BY a.published_date DESC&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With only one query, every post is retrieved. Without a recursive CTE, multiple successive queries will be necessary. The drawback is it depends on the database engine and could not be available in some databases.&lt;/p&gt;

&lt;p&gt;Moreover, using the &lt;code&gt;ResultSetMappingBuilder&lt;/code&gt; requires all columns needed to hydrate the entity should be included in the &lt;code&gt;SELECT&lt;/code&gt;. Otherwise, the entity will be partially hydrated without any explicit notice or error.&lt;/p&gt;
</description>
                    <pubDate>Sat, 09 May 2026 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2026/05/09/execute-cte-queries-using-doctrine-orm.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2026/05/09/execute-cte-queries-using-doctrine-orm.html</guid>
                </item>
            
        
            
        
            
        
            
                <item>
                    <title>Learning a New Programming Language in the AI era</title>
                    <description>&lt;p&gt;Every year, I try to learn a new programming language. Over the years, I’ve learned or (re)discovered languages like &lt;a href=&quot;https://www.ruby-lang.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Ruby&lt;/a&gt;, &lt;a href=&quot;https://elixir-lang.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Elixir&lt;/a&gt;, &lt;a href=&quot;https://rust-lang.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Rust&lt;/a&gt;, &lt;a href=&quot;https://go.dev &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Go&lt;/a&gt;, &lt;a href=&quot;https://www.python.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Python&lt;/a&gt; or &lt;a href=&quot;https://www.typescriptlang.org &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Typescript&lt;/a&gt;. This year, it will be &lt;a href=&quot;https://learn.microsoft.com/dotnet/csharp/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;C#&lt;/a&gt;. But in the AI era, where lesser and lesser code is humanly written, still it interesting ?&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;In my own opinion, the answer is: yes, it is. Even if the AI is everywhere, in our every day or professional life. I use it to explore and generate code to help me find solutions and to accelerate software implementation. But the more we use AI, the less our brain is used to manipulate basic concepts of our programming job.&lt;/p&gt;

&lt;p&gt;So it’s necessary to be active and learning a new language is a way to review basics. I’ve always liked to write code, thinking about what I want to build and then be able to materialize it. It’s also a way to keep my judgment capacity to evaluate code quality.&lt;/p&gt;

&lt;p&gt;Moreover, by learning a new programming language, I can discover new practices and principles than I used to work with my primary language. Each language has its own philosophy and it’s a way to discover some problems with a new angle.&lt;/p&gt;

&lt;p&gt;This year, I’ve chosen C# because the language has evolved over the years. I also appreciate the community behind it.&lt;/p&gt;

&lt;p&gt;To conclude, AI can write code, AI can generate a full application without any human intervention. But it’s important to be able to judge the output and it’s possible only if I have the competence to do the job. That’s why, keeping hands on, I continue to learn and practice programming “manually”.&lt;/p&gt;
</description>
                    <pubDate>Sat, 02 May 2026 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2026/05/02/learning-a-new-programming-language-in-the-ai-era.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2026/05/02/learning-a-new-programming-language-in-the-ai-era.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Feature teams organization</title>
                    <description>&lt;p&gt;When a tech company grows, teams organize themselves around technical divisions (front-end, back-end, infrastructure, DevOps…). This division is quite natural, as it brings groups with similar skills. But this organization isn’t without problems.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;IMO, this organization has many downsides. Features are designed and split across multiple teams. Responsibilities are diluted, and teams can blame each other when problems occur. With this organization, there’s no global vision, teams don’t understand other teams’ challenges. It generates silos and company performance decreases.&lt;/p&gt;

&lt;p&gt;High-performing teams organize themselves around functional splits. Each team is autonomous and fully responsible for a given domain. Those teams are multidisciplinary. Team members know everyone’s challenges. They move forward together toward a common goal. Responsibilities are no longer split. The whole team is responsible for the domain.&lt;/p&gt;

&lt;p&gt;Autonomy is the key to ownership.&lt;/p&gt;
</description>
                    <pubDate>Wed, 17 Sep 2025 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2025/09/17/feature-teams-organization.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2025/09/17/feature-teams-organization.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Focus on added value using AI</title>
                    <description>&lt;p&gt;With AI, developers can focus on the essential: added value for users and software architecture.&lt;/p&gt;

&lt;p&gt;Personally, I’ve been too much focused on code and technical quality. But with the AI, writing code becomes secondary, allowing us to concentrate on what really matters: solving user problems and create sustainable software architecture.&lt;/p&gt;

&lt;p&gt;But AI should not be used without control. It should be a guide on what to implement and how. Developers should be architects and AI should only write technical implementations.&lt;/p&gt;
</description>
                    <pubDate>Mon, 18 Aug 2025 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2025/08/18/focus-on-added-value-using-ai.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2025/08/18/focus-on-added-value-using-ai.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Named constructors as multiple constructors alternative</title>
                    <description>&lt;p&gt;Unlike other programming languages, PHP does not allow for multiple constructors in a class. However, defining multiple constructors can be beneficial in various scenarios, such as creating an object from different data types. If this functionality is not available in PHP, named constructors can be used as an alternative solution.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;What is a named constructor? It is a static method that we can use to create an object instance. These methods have the advantage of being more explicit than the basic constructor, as they can add meaning to the object’s construction.&lt;/p&gt;

&lt;p&gt;Consider the following example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;readonly class Color
{
    public function __construct(
        public int $red,
        public int $blue,
        public int $green,
    ) {}
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The previous code defines an object that stores a color. This color consists of three levels: red, green and blue. The main constructor fills in these corresponding values. However, sometimes it may be useful to create a color from its hexadecimal value. In this case, we could introduce a constructor called &lt;code&gt;fromHexCode&lt;/code&gt; to do this task:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;readonly class Color
{
    public static function fromHexCode(string $code): self
    {
        $code = ltrim($code, &amp;#39;#&amp;#39;);

        $red = hexdec(substr($code, 0, 2));
        $blue = hexdec(substr($code, 2, 2));
        $green = hexdec(substr($code, 4, 2));

        return new self($red, $blue, $green);
    }

    public function __construct(
        public int $red,
        public int $blue,
        public int $green,
    ) {}
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Named constructors can simplify object creation by providing default values based on the context. They also encapsulate the complex creation logic, making the code easier to read and understand.&lt;/p&gt;

&lt;p&gt;This technique is also popular in &lt;em&gt;Domain Driven Design&lt;/em&gt;. It can help clarify the business process that led to the data creation. For example, consider user registration in an application:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class User
{
    public static function fromRegistration(string $name, string $email, string $password): self
    {
        $user = new self($name, $email, $password);

        // additionnal business logic related to registration

        return $user;
    }

    public static function fromSocialLogin(string $name, string $email): self
    {
        $user = new self($name, $email);

        // additionnal business logic related to social login

        return $user;
    }

    private function __construct(
        public string $name,
        public string $email,
        public ?string $password = null,
    ) {}
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This last example introduces a &lt;code&gt;User&lt;/code&gt; class with a private constructor. To create an object instance, a named constructor must be used. The constructor choice will depend on the business context. This approach allows us to see user creation requirements depends on a specific use case.&lt;/p&gt;

&lt;p&gt;When a user registers via the application, it should provide a name, an email and a password. However if the same user is logged through a social login, we only need the name and the email. The password is not necessary when using an external login system.&lt;/p&gt;

&lt;p&gt;In summary, using named constructors can significantly improve the code base and enhance code readability. I highly recommend that you explore this technique as it under-exploited.&lt;/p&gt;
</description>
                    <pubDate>Tue, 28 Jan 2025 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2025/01/28/named-constructors-as-multiple-constructors-alternative.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2025/01/28/named-constructors-as-multiple-constructors-alternative.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Test your Docker images</title>
                    <description>&lt;p&gt;Do you build Docker images regularly? You certainly know that just because your image was built without errors, it doesn’t mean it works as you expect. Building a Docker image, like any code, must be verified and validated. As your code, you can write tests that will check your images.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;The tool I personally use is called &lt;a href=&quot;https://github.com/GoogleContainerTools/container-structure-test &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Container Structure Test&lt;/a&gt;. Developed by Google, it allows you to write unit tests for your Docker images. Written in Go, it’s available as a binary compatible with most operating systems.&lt;/p&gt;

&lt;p&gt;With &lt;em&gt;Container Structure Test&lt;/em&gt;, you will:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Run commands inside the container and verify the output or any errors produced,&lt;/li&gt;
  &lt;li&gt;Test file existence,&lt;/li&gt;
  &lt;li&gt;Check file contents  (including its associated metadata),&lt;/li&gt;
  &lt;li&gt;Verify the configuration of the container itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once downloaded, setting up tests is done by writing rules in a YAML file. Here’s an example configuration I use to test the construction of Docker images running PHP projects:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;schemaVersion: &amp;quot;2.0.0&amp;quot;

# Vérification de la présence de certaines variables d&amp;#39;environnement
globalEnvVars:
    - key: GITHUB_TOKEN
      value: github-token

commandTests:
    - name: &amp;quot;Symfony CLI installed&amp;quot;
      command: &amp;quot;which&amp;quot;
      args: [&amp;quot;symfony&amp;quot;]
      exitCode: 0
    - name: &amp;quot;Check PHP extensions&amp;quot;
      command: &amp;quot;php&amp;quot;
      args: [&amp;quot;-m&amp;quot;]
      expectedOutput:
          - &amp;quot;amqp&amp;quot;
          # ...


fileExistenceTests:
- name: &amp;#39;Configuration PHP&amp;#39;
  path: &amp;#39;etc/php/8.3/php.ini&amp;#39;
  shouldExist: false

fileContentTests:
- name: &amp;#39;Linux Version&amp;#39;
  path: &amp;#39;/etc/os-release&amp;#39;
  expectedContents: [&amp;quot;VERSION_ID=3.14.2&amp;quot;,&amp;quot;NAME=\&amp;quot;Alpine Linux\&amp;quot;&amp;quot;]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When the setup is complete, you just need this command:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;container-structure-test test --image my-registry.jdecool.fr/php:8.3 --config php-tests.yaml

=======================================
====== Test file: php-tests.yaml ======
=======================================
=== RUN: Command Test: Symfony CLI installed
--- PASS
duration: 306.265278ms
stdout: [...]

=== RUN: Command Test: Check PHP extensions
--- PASS
duration: 303.302806ms
stdout: [...]

=== RUN: Command Test: Configuration PHP
--- PASS
duration: 276.503651ms
stdout: [...]

=== RUN: Command Test: Linux Version
duration: 276.503651ms
stdout: [...]

=======================================
=============== RESULTS ===============
=======================================
Passes:      4
Failures:    0
Duration:    789.707605ms
Total tests: 4

PASS
&lt;/code&gt;&lt;/pre&gt;
</description>
                    <pubDate>Tue, 29 Oct 2024 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2024/10/29/test-your-docker-images.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2024/10/29/test-your-docker-images.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Join me on Mastodon and Bluesky</title>
                    <description>&lt;p&gt;I’ve been sharing technology and programming articles on &lt;a href=&quot;https://twitter.com/jdecool &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;X (Twitter)&lt;/a&gt; for several years.&lt;/p&gt;

&lt;p&gt;Unfortunately, the Twitter ecosystem has been deteriorating, and more and more people migrate to alternatives.&lt;/p&gt;

&lt;p&gt;That’s why, all my social publications are now also available on &lt;a href=&quot;https://phpc.social/@jdecool &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Mastodon&lt;/a&gt; and &lt;a href=&quot;https://bsky.app/profile/jdecool.bsky.social &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Bluesky&lt;/a&gt;.&lt;/p&gt;

&lt;!--more--&gt;
</description>
                    <pubDate>Mon, 06 May 2024 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2024/05/06/join-me-on-maston-and-bluesky.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2024/05/06/join-me-on-maston-and-bluesky.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Backing up your MySQL database without disturbing your production</title>
                    <description>&lt;p&gt;In most cases, to back up a MySQL database, the &lt;code&gt;mysqldump&lt;/code&gt; command is used (it’s the official utility provided by the database). But did you know that the command is risky and can disturb your production?&lt;/p&gt;

&lt;p&gt;I recently experimented it when trying to back up a large database. Because by default, the tool will lock tables, making them inaccessible. If you don’t have a lot of data, there won’t impact. But otherwise, it can cause a service interruption for the duration of the operation.&lt;/p&gt;

&lt;p&gt;To avoid this, &lt;code&gt;mysqldump&lt;/code&gt; offers a number of options that can help you avoid these issues. The two most important I use systematically are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;--single-transaction&lt;/code&gt;: allow dumping the consistent state of the database at the time when &lt;code&gt;START TRANSACTION&lt;/code&gt; was issued without blocking any applications,&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;--skip-lock-tables&lt;/code&gt;: avoid locking table when dumping it.&lt;/li&gt;
&lt;/ul&gt;
</description>
                    <pubDate>Sun, 18 Feb 2024 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2024/02/18/backing-up-your-mysql-database-without-disturbing-production.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2024/02/18/backing-up-your-mysql-database-without-disturbing-production.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>How to use PHPUnit 10 with Symfony</title>
                    <description>&lt;p&gt;PHPUnit has been release 8 months ago (the 3rd of February 2023). But if we try to use it in a Symfony project using the &lt;code&gt;symfony/phpunit-bridge&lt;/code&gt; component, a &lt;code&gt;PHP Fatal error:  Uncaught Error: Class &quot;PHPUnit\TextUI\Command&lt;/code&gt; occurred.&lt;/p&gt;

&lt;!--more--&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;PHP Fatal error:  Uncaught Error: Class &quot;PHPUnit\TextUI\Command&quot; not found in /home/jdecool/Workspace/sandbox/test/bin/phpunit:11
Stack trace:
#0 {main}
  thrown in /home/jdecool/Workspace/sandbox/test/bin/phpunit on line 11

Fatal error: Uncaught Error: Class &quot;PHPUnit\TextUI\Command&quot; not found in /home/jdecool/Workspace/sandbox/test/bin/phpunit on line 11

Error: Class &quot;PHPUnit\TextUI\Command&quot; not found in /home/jdecool/Workspace/sandbox/test/bin/phpunit on line 11

Call Stack:
    0.0001     396248   1. {main}() /home/jdecool/Workspace/sandbox/test/bin/phpunit:0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The Symfony script tries to use a deleted PHPUnit class. While there are several PRs to fix the problem, but none are currently merged.&lt;/p&gt;

&lt;p&gt;If you want to solve this issue, you can simply update the &lt;code&gt;bin/phpunit&lt;/code&gt; file using this patch:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-patch&quot;&gt;-    PHPUnit\TextUI\Command::main();
+    exit((new PHPUnit\TextUI\Application)-&amp;gt;run($_SERVER[&apos;argv&apos;]));
&lt;/code&gt;&lt;/pre&gt;
</description>
                    <pubDate>Tue, 10 Oct 2023 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2023/10/10/how-to-use-phpunit-10-with-symfony.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2023/10/10/how-to-use-phpunit-10-with-symfony.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
                <item>
                    <title>The meeting pertinence</title>
                    <description>&lt;p&gt;This is not new, I’ve been hearing it for several years, everybody wants to limit meetings. One of the latest examples is &lt;a href=&quot;https://www.forbes.com/sites/jenamcgregor/2023/01/03/shopify-is-canceling-all-meetings-with-more-than-two-people-from-workers-calendars-and-urging-few-to-be-added-back/ &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Shopify, which is considering eliminating all recurring meetings with more than two people&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Yes, meetings can be unproductive and a waste of time for attendees. In reality, the cause is very often a lack of preparation. But there is something very important in meetings: &lt;strong&gt;meetings aim to communicate and collaborate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When working on a team, I think it’s important and essential to have sometimes synchronous communication, where everyone can discuss, exchange, hear the same thing at the same time. This is what builds teams, contributes to cohesion and allows them to share a common vision. It’s not about working in isolation.&lt;/p&gt;

&lt;p&gt;Indeed, this is not easy and one of the major problems is that there is always the same people who speak, sometimes they crush other people (even involuntarily). This is a bias that is not easy to avoid but we should be careful about it!&lt;/p&gt;

&lt;p&gt;They are many techniques and tools to avoid traps. For me, I’m using a lot of agile tools, rituals and ceremonies.&lt;/p&gt;
</description>
                    <pubDate>Sun, 08 Jan 2023 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2023/01/08/the-meeting-pertinence.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2023/01/08/the-meeting-pertinence.html</guid>
                </item>
            
        
            
        
            
                <item>
                    <title>About the code coverage</title>
                    <description>&lt;p&gt;When starting to write unit tests in a project and we want to have some test metric, we usually start with the &lt;em&gt;code coverage&lt;/em&gt;. This indicator is very used, but it’s also very criticized.&lt;/p&gt;

&lt;p&gt;The code coverage is a measure of the executed source code lines during the test process. It means it counts the production code lines browses by tests. The problem is that lines are counted even if there is no test assertions.&lt;/p&gt;

&lt;p&gt;For example, taking this code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function calculate(string $op, int $x, int $y): int
{
    return match ($op) {
        &amp;#39;+&amp;#39; =&amp;gt; $x + $y,
        &amp;#39;-&amp;#39; =&amp;gt; $x - $y,
        &amp;#39;*&amp;#39; =&amp;gt; $x * $y,
        &amp;#39;/&amp;#39; =&amp;gt; $x / $y,
        default =&amp;gt; throw new \InvalidArgumentException(),
    };
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And its associated test:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class CalculatorTest extends TestCase
{
    public function testCalculateMethod(): void
    {
        $resultat = calculate(&amp;#39;+&amp;#39;, 1, 5);

        $this-&amp;gt;assertIsInt($resultat);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We obtain the following code coverage result:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;Summary:
  Classes: 100.00% (1/1)
  Methods: 100.00% (1/1)
  Lines:   100.00% (7/7)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The test has no pertinent assertion, but we have 100% code coverage. That’s why code coverage is criticized.&lt;/p&gt;

&lt;p&gt;This is also why we should consider code coverage as a project negative indicator and not as a quality indicator. A project with a bad code coverage indicates missing tests. Whereas, if it has a good code coverage, it is not enough to measure the test quality. We should complete it with other metrics.&lt;/p&gt;

&lt;p&gt;Note that there is another metric related to the code coverage: &lt;strong&gt;the branch coverage&lt;/strong&gt;. It consists to evaluate and check if each control structure (such as &lt;code&gt;if&lt;/code&gt; or &lt;code&gt;case&lt;/code&gt; statements) has been executed.&lt;/p&gt;

&lt;p&gt;It we take our previous example, the branch coverage result would be:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;Summary:
  Classes:  0.00% (0/1)
  Methods:  0.00% (0/1)
  Paths:    20.00% (1/5)
  Branches: 42.86% (3/7)
  Lines:    100.00% (7/7)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We can easily see the difference. Every line of code has been executed, but tests only explored one of the five possible execution paths. This clearly indicates that tests are not relevant because they cover a few behaviors.&lt;/p&gt;

&lt;p&gt;As we saw, branch coverage is a better indicator than code coverage. Nevertheless, we should be careful as it is expensive to calculate (time, CPU, memory, …) and it can slow your test suite.&lt;/p&gt;
</description>
                    <pubDate>Tue, 03 Jan 2023 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2023/01/03/about-the-code-coverage.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2023/01/03/about-the-code-coverage.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Travel in your PHP memory with PHP Meminfo</title>
                    <description>&lt;p&gt;Do you really know how your PHP project consumes the memory ? If the answer is no, you should consider using &lt;a href=&quot;https://github.com/BitOne/php-meminfo &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;PHP Meminfo&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;PHP Meminfo is a PHP extension that gives you insights on the PHP memory content. Its main goal is to help you understand memory leaks: by looking at data present in memory, you can better understand your application behaviour.&lt;/p&gt;

&lt;p&gt;The version 1.0.0 just released with PHP 7 support, more complete and better documentation.&lt;/p&gt;

&lt;p&gt;If you are a macOS user and you use &lt;a href=&quot;https://brew.sh &quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;Homebrew&lt;/code&gt;&lt;/a&gt; as package manager, you can install the extension easily, &lt;a href=&quot;javascript:;&quot; class=&quot;broken-link&quot; rel=&quot;nofollow&quot; data-original-url=&quot;http://formulae.brew.sh/search/meminfo &quot;&gt;as a package exists for each compatible PHP version&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Mon, 27 Nov 2017 00:00:00 +0100</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2017/11/27/travel-in-your-php-memory-with-php-meminfo.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2017/11/27/travel-in-your-php-memory-with-php-meminfo.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>Manage Composer dependencies in your monorepository project</title>
                    <description>&lt;p&gt;Having a monorepository project means to get all applications and components at
the same place. This post isn’t about the avantages or drawbacks of this project
management strategy, a lot of developer &lt;a href=&quot;http://danluu.com/monorepo/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;blogged about the advantages&lt;/a&gt;
or &lt;a href=&quot;http://engineeredweb.com/blog/2016/monorepo-dangers/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;drawbacks&lt;/a&gt;.
This post is about how to manage your Composer dependencies in the specific case
of monorepository based project.&lt;/p&gt;

&lt;p&gt;First, have a look of a project folder structure :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;applications/
    api/
        composer.json
    backend/
        composer.json
    frontend/
        composer.json
    worker/
        composer.json
component/
    package1/
        composer.json
    package2/
        composer.json&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The challenge is to define how the differents applications can easily use the
2 components &lt;code&gt;package1&lt;/code&gt; and &lt;code&gt;package2&lt;/code&gt;. Maybe your first idea is to configure
&lt;a href=&quot;https://getcomposer.org/doc/01-basic-usage.md#autoloading&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Composer autoloading&lt;/a&gt;
to reference all components which are in the repository. This looks like :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &amp;quot;autoload&amp;quot;: {
    &amp;quot;psr-4&amp;quot;: {
      &amp;quot;Vendor\\Package1\\&amp;quot;: &amp;quot;../../component/package1/src&amp;quot;,
      &amp;quot;Vendor\\Package2\\&amp;quot;: &amp;quot;../../component/package2/src&amp;quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This method works, but it’s not the best solution because the &lt;code&gt;composer.json&lt;/code&gt; file
of different components is not used. And this is the file which is used to define
components requirement and configuration. Furthermore, there is a duplication of
autoloading configuration: in the components and in each application will used its.&lt;/p&gt;

&lt;p&gt;If you read the Composer documentation, there is a concept of
&lt;a href=&quot;https://getcomposer.org/doc/05-repositories.md#repository&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;“repositories”&lt;/a&gt;.
A repository is a package source. It’s a list of packages/versions. Composer will
look in all your repositories to find the packages your project requires.&lt;/p&gt;

&lt;p&gt;It’s possible to add new &lt;code&gt;repository&lt;/code&gt; sources like &lt;code&gt;github&lt;/code&gt;, &lt;code&gt;gitlab&lt;/code&gt; or &lt;code&gt;vcs&lt;/code&gt;
and even more. The one which is very interestant in our case is &lt;code&gt;path&lt;/code&gt;. This will
allow to refer to a local repository of the computer.&lt;/p&gt;

&lt;p&gt;We’re going to update our &lt;code&gt;composer.json&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{
  &amp;quot;repositories&amp;quot;: [
    {
      &amp;quot;type&amp;quot;: &amp;quot;path&amp;quot;,
      &amp;quot;url&amp;quot;: &amp;quot;../../component/package1&amp;quot;
    },
    {
      &amp;quot;type&amp;quot;: &amp;quot;path&amp;quot;,
      &amp;quot;url&amp;quot;: &amp;quot;../../component/package2&amp;quot;
    }
  ],
  &amp;quot;require&amp;quot;: {
      &amp;quot;vendor/package1&amp;quot;: &amp;quot;*&amp;quot;,
      &amp;quot;vendor/package2&amp;quot;: &amp;quot;*&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now, the Composer configuration refers to our local repository, we don’t need to
override the component configuration, whereas Composer will load it as any other
dependency.&lt;/p&gt;

&lt;p&gt;While I made some search about the best way to manage Composer dependencies in a
monorepository, I discovered an experimental project, which is an
&lt;a href=&quot;https://github.com/beberlei/composer-monorepo-plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Composer plugin&lt;/a&gt;
dedicated to manage this project structure. I didn’t test it, but it could be interesting.
The project author &lt;a href=&quot;https://beberlei.de/2016/05/28/composer_monorepo_plugin_previously_called_fiddler.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;have blogged about the tool&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Tue, 11 Jul 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2017/07/11/manage-composer-dependencies-in-monorepository-project.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2017/07/11/manage-composer-dependencies-in-monorepository-project.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
                <item>
                    <title>RSS is dead, long life to JSONFeed</title>
                    <description>&lt;p&gt;You may not know about it, but it want to replace the RSS format.
The first version of new syndication format called
&lt;a href=&quot;http://jsonfeed.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;JSONFeed&lt;/a&gt;
was released on May 17, 2017.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://fr.wikipedia.org/wiki/RSS&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;RSS format&lt;/a&gt;
which is massively used since 1999 allow website to publish a news feed. It has evolved
in 2003 to become a new format called &lt;a href=&quot;https://fr.wikipedia.org/wiki/Atom&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Atom&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;JSONFeed authors have noticed that JSON has become the developers’ choice for APIs,
and that developers will often go out of their way to avoid XML. JSON is simpler to
read and write, and it’s less prone to bugs. They developed JSON Feed, a format similar
to RSS and Atom but in JSON. It reflects the lessons learned from our years of work
reading and publishing feeds.&lt;/p&gt;

&lt;p&gt;A new format that we must follow in the next months.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you are a PHP developer, I start to create a library to read and write JSONFeed
content. It is available on &lt;a href=&quot;https://github.com/jdecool/jsonfeed/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Github&lt;/a&gt;.
Feel free to contribute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;I have also updated my website to allow you to follow updates throw a JSONFeed.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Wed, 31 May 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2017/05/31/rss-is-dead-long-life-to-jsonfeed.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2017/05/31/rss-is-dead-long-life-to-jsonfeed.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>How to use Chrome Headless with Behat</title>
                    <description>&lt;p&gt;Headless Chrome is shipping in Chrome 59 (currently in beta), it seems that we
can use the browser without UI. It is very interesting for testing and automated
purpose. In this post, we are going to configure Behat (a PHP framework for
autotesting your business expectations) to use Chrome in headless mode.&lt;/p&gt;

&lt;p&gt;This new Chrome feature interest a large part of the Web community. So much, that
the maintainer of PhantomJS, a headless browser which is also based on Webkit,
&lt;a href=&quot;https://groups.google.com/d/msg/phantomjs/9aI5d-LDuNE/5Z3SMZrqAQAJ&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;announced the end of the project&lt;/a&gt;.
So, if you used this in your Behat scenarios, you should change the browser you
used.&lt;/p&gt;

&lt;p&gt;This is an sample of the Behat configuration I use for PhantomJS :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# behat.yml
default:
    extensions:
        # ...
        Behat\MinkExtension:
            base_url: http://project.dev
            sessions:
                default:
                    selenium2:
                        wd_host: http://localhost:4444/wd/hub&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we have to make some change to be able to use Chrome browser. First, we have
to add the &lt;a href=&quot;https://sites.google.com/a/chromium.org/chromedriver/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;ChromeDriver&lt;/a&gt;.
Next, we need to add some configuration directory in our &lt;code&gt;behat.yml&lt;/code&gt; file :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;# behat.yml
default:
    extensions:
        # ...
        Behat\MinkExtension:
            base_url: http://project.dev
            sessions:
                default:
                    selenium2:
                        browser: chrome
                        wd_host: http://localhost:4444/wd/hub
                        capabilities:
                            chrome:
                                switches:
                                    - &amp;quot;--headless&amp;quot;
                                    - &amp;quot;--disable-gpu&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;There are some little changes if the configuration file. We begin by defining the
&lt;code&gt;browser&lt;/code&gt; we want to use. Then we set up some &lt;code&gt;capabilities&lt;/code&gt; (options that can
add to customize and configure our Chrome session). We add the &lt;code&gt;--headless&lt;/code&gt; argument
to use the headless mode in combination to &lt;code&gt;--disable-gpu&lt;/code&gt; which is temporarily
needed for now.&lt;/p&gt;

&lt;p&gt;Et voilà ! By adding 5 lines of configuration we have replaced the PhantomJS
browser usage by Google Chrome with “headless” mode.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://gist.github.com/jdecool/ec2dbc08e79e66d27b3d56d10ea28bf4&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;The full configuration file is available on Gist&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
                    <pubDate>Mon, 08 May 2017 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2017/05/08/how-to-use-chrome-headless-with-behat.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2017/05/08/how-to-use-chrome-headless-with-behat.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
                <item>
                    <title>How to use a Select2 input with Behat</title>
                    <description>&lt;p&gt;If you are a PHP developer, you might know Behat, the most popuplar Behavior Driven
Development (BDD) framework for PHP. Personnaly, I frequently annoyed when I have
to write scenario with an UI which is using &lt;a href=&quot;https://select2.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Select2&lt;/a&gt;,
because it’s not possilbe to manipulate the field natively with Behat and the Mink
extension.&lt;/p&gt;

&lt;p&gt;To do this work, you need to create a new Behat context where you will add custom
actions needed to control the Select2 list. Given there is a very few explanations
on the subject, I decide to share the code to do that.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
 * @When /^(?:|I )fill in select2 input &amp;quot;(?P&amp;lt;field&amp;gt;(?:[^&amp;quot;]|\\&amp;quot;)*)&amp;quot; with &amp;quot;(?P&amp;lt;value&amp;gt;(?:[^&amp;quot;]|\\&amp;quot;)*)&amp;quot; and select &amp;quot;(?P&amp;lt;entry&amp;gt;(?:[^&amp;quot;]|\\&amp;quot;)*)&amp;quot;$/
 */
public function fillInSelectInputWithAndSelect($field, $value, $entry)
{
    $page = $this-&amp;gt;getSession()-&amp;gt;getPage();

    $inputField = $page-&amp;gt;find(&amp;#39;css&amp;#39;, $field);
    if (!$inputField) {
        throw new \Exception(&amp;#39;No field found&amp;#39;);
    }

    $choice = $inputField-&amp;gt;getParent()-&amp;gt;find(&amp;#39;css&amp;#39;, &amp;#39;.select2-selection&amp;#39;);
    if (!$choice) {
        throw new \Exception(&amp;#39;No select2 choice found&amp;#39;);
    }
    $choice-&amp;gt;press();

    $select2Input = $page-&amp;gt;find(&amp;#39;css&amp;#39;, &amp;#39;.select2-search__field&amp;#39;);
    if (!$select2Input) {
        throw new \Exception(&amp;#39;No input found&amp;#39;);
    }
    $select2Input-&amp;gt;setValue($value);

    $this-&amp;gt;getSession()-&amp;gt;wait(1000);

    $chosenResults = $page-&amp;gt;findAll(&amp;#39;css&amp;#39;, &amp;#39;.select2-results li&amp;#39;);
    foreach ($chosenResults as $result) {
        if ($result-&amp;gt;getText() == $entry) {
            $result-&amp;gt;click();
            break;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you are interested by manipulating a Select2 with Behat, please consider this
&lt;a href=&quot;https://packagist.org/packages/novaway/common-contexts&quot; target=&quot;_target&quot;&gt;Behat extension&lt;/a&gt;
created and open sourced by my current company. It’s can be used with Composer and
provide the most common feature for Select2. It’s also available on
&lt;a href=&quot;https://github.com/novaway/BehatCommonContext&quot; target=&quot;_target&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
</description>
                    <pubDate>Mon, 26 Sep 2016 00:00:00 +0200</pubDate>
                    <link>https://www.jdecool.fr/en/blog/2016/09/26/how-to-use-select2-input-with-behat.html</link>
                    <guid isPermaLink="true">https://www.jdecool.fr/en/blog/2016/09/26/how-to-use-select2-input-with-behat.html</guid>
                </item>
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
            
        
    </channel>
</rss>
