<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="https://viktorprogger.name/atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US">
  <id>https://viktorprogger.name/</id>
  <title>Viktor Babanov</title>
  <subtitle>Viktor Babanov&amp;apos;s blog about development-related technologies, technics and processes</subtitle>
  <updated>2026-08-29T21:02:56.899Z</updated>
  <generator>@vuepress/plugin-feed</generator>
  <link rel="self" href="https://viktorprogger.name/atom.xml"/>
  <link rel="alternate" href="https://viktorprogger.name/"/>
  <category term="Development Techniques"/>
  <category term="Dont do this"/>
  <category term="Personal"/>
  <category term="Development process"/>
  <entry>
    <title type="text">My dev environment for php projects</title>
    <id>https://viktorprogger.name/posts/dev-environment-docker-for-php-projects-with-roadrunner.html</id>
    <link href="https://viktorprogger.name/posts/dev-environment-docker-for-php-projects-with-roadrunner.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>Project development requires not only programming skills but also the ability to set up an effective working environment. In this article, I'll share my experience in creating a dev environment for PHP projects using <strong>Docker</strong>. I'll explain how to organize the file structure, configure <code>Dockerfile</code> and <code>docker-compose.yml</code>, and share useful tips on optimization and security. Whether you're working on a pet project or preparing for team development, these practices will help you create a reliable and flexible development environment.</p>
<p><img src="/dev-environment.webp" alt="Dev environment with Docker (generated by ChatGPT)" title="Dev environment with Docker (generated by ChatGPT)"></p>
<p>This environment is based on my favorite and most frequently used tools:</p>
<ul>
<li><a href="https://www.docker.com/" target="_blank" rel="noopener noreferrer">Docker</a> for containerization</li>
<li><a href="https://docs.docker.com/compose/" target="_blank" rel="noopener noreferrer">Docker compose</a> for container management</li>
<li>Latest version of PHP</li>
<li><a href="https://getcomposer.org/" target="_blank" rel="noopener noreferrer">Composer</a> for PHP dependency management</li>
<li><a href="https://xdebug.org/" target="_blank" rel="noopener noreferrer">xDebug</a> for application debugging</li>
<li><a href="https://roadrunner.dev/" target="_blank" rel="noopener noreferrer">RoadRunner</a> for running the application in Long-Running mode</li>
<li>PHP framework Yii3</li>
<li><a href="https://www.postgresql.org/" target="_blank" rel="noopener noreferrer">PostgreSQL</a> database, as it supports keep-alive connections, <a href="https://stackoverflow.com/questions/23399111/safely-keeping-mysql-connections-alive" target="_blank" rel="noopener noreferrer">unlike some others</a>.</li>
</ul>
]]></summary>
    <content type="html"><![CDATA[<p>Project development requires not only programming skills but also the ability to set up an effective working environment. In this article, I'll share my experience in creating a dev environment for PHP projects using <strong>Docker</strong>. I'll explain how to organize the file structure, configure <code>Dockerfile</code> and <code>docker-compose.yml</code>, and share useful tips on optimization and security. Whether you're working on a pet project or preparing for team development, these practices will help you create a reliable and flexible development environment.</p>
<p><img src="/dev-environment.webp" alt="Dev environment with Docker (generated by ChatGPT)" title="Dev environment with Docker (generated by ChatGPT)"></p>
<p>This environment is based on my favorite and most frequently used tools:</p>
<ul>
<li><a href="https://www.docker.com/" target="_blank" rel="noopener noreferrer">Docker</a> for containerization</li>
<li><a href="https://docs.docker.com/compose/" target="_blank" rel="noopener noreferrer">Docker compose</a> for container management</li>
<li>Latest version of PHP</li>
<li><a href="https://getcomposer.org/" target="_blank" rel="noopener noreferrer">Composer</a> for PHP dependency management</li>
<li><a href="https://xdebug.org/" target="_blank" rel="noopener noreferrer">xDebug</a> for application debugging</li>
<li><a href="https://roadrunner.dev/" target="_blank" rel="noopener noreferrer">RoadRunner</a> for running the application in Long-Running mode</li>
<li>PHP framework Yii3</li>
<li><a href="https://www.postgresql.org/" target="_blank" rel="noopener noreferrer">PostgreSQL</a> database, as it supports keep-alive connections, <a href="https://stackoverflow.com/questions/23399111/safely-keeping-mysql-connections-alive" target="_blank" rel="noopener noreferrer">unlike some others</a>.</li>
</ul>
<!-- more -->
<p>Unfortunately, I haven't yet formalized my familiar development environment into a template that could be used with the <code>composer create project</code> command. But it's not very convenient anyway, as the template changes and evolves from project to project. I take the version from the previous project for each new one. However, the basic principles haven't changed for a long time, and today I'll show you primarily these.</p>
<h2>Dockerfile and state preservation</h2>
<p>First, in the root of each project, I have a <code>.docker</code> folder where I put all project-specific things:</p>
<ul>
<li><code>Dockerfile</code> for PHP container images and others</li>
<li>Configs that are embedded inside container images or connected as volumes. For example, the main <code>php.ini</code> is added inside the image, while xDebug enablement is connected as a volume only in the local development environment</li>
<li>Container state. I always save the DB state to disk (it's impossible not to do so in production), and in the dev environment - also the composer cache to make its commands run faster.
Here's an example structure that I commit to the repository, from my latest project:</li>
</ul>
<div class="language- line-numbers-mode" data-highlighter="shiki" data-ext style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-"><span class="line"><span>.docker</span></span>
<span class="line"><span>├──data</span></span>
<span class="line"><span>│  ├──composer</span></span>
<span class="line"><span>│     └──.gitignore</span></span>
<span class="line"><span>│  └──postgres</span></span>
<span class="line"><span>│     └──.gitignore</span></span>
<span class="line"><span>└──php</span></span>
<span class="line"><span>   ├──Dockerfile</span></span>
<span class="line"><span>   ├──php.ini</span></span>
<span class="line"><span>   └──xdebug.ini</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><ul>
<li>The <code>data</code> folder is for saving state from containers between restarts. Inside each subfolder is a <code>.gitignore</code> file with two lines: <code>*</code> to ignore everything in this folder and <code>!.gitignore</code> to commit this file itself.</li>
<li>The <code>php</code> folder contains <code>Dockerfile</code> and configs for the PHP container</li>
<li>If I need to build any other containers - I'll create another folder next to it, and put <code>Dockerfile</code> and all necessary configs in it as well.</li>
</ul>
<p>And here's the starting version of my Dockerfile:</p>
<div class="language-dockerfile line-numbers-mode" data-highlighter="shiki" data-ext="dockerfile" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-dockerfile"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">FROM</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> php:8.3-cli-bullseye</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">RUN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> apt update \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x26;&#x26; apt install -y  \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        libicu-dev \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        linux-headers-generic \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        zip \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        libpq-dev \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x26;&#x26; apt install -y $PHPIZE_DEPS \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x26;&#x26; docker-php-ext-configure intl \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x26;&#x26; docker-php-ext-install -j$(nproc) \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        pdo \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        pdo_pgsql \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        pgsql \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        pcntl \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        sockets \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        intl \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        opcache \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x26;&#x26; pecl install xdebug \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x26;&#x26; docker-php-ext-enable intl \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x26;&#x26; pecl clear-cache \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x26;&#x26; apt purge -y $PHPIZE_DEPS \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x26;&#x26; apt clean</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">COPY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> --from=composer:latest /usr/bin/composer /usr/bin/composer</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ADD</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> .docker/php/php.ini /usr/local/etc/php/conf.d/40-custom.ini</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">WORKDIR</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> /var/www</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ARG</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> USER_ID=1000</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">ARG</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> GROUP_ID=1000</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">RUN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> groupadd -g $GROUP_ID appuser &#x26;&#x26; \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    useradd -m -N -g $GROUP_ID -u $USER_ID appuser &#x26;&#x26; \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    usermod -L appuser &#x26;&#x26; \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    chown appuser:appuser .</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">USER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> appuser</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">COPY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> --chown=appuser:appuser ./composer.* configuration.php ./</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">RUN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> composer install \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        --prefer-dist \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        --no-ansi \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        --no-dev \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        --no-interaction \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        --no-plugins \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        --no-progress \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        --no-scripts \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x26;&#x26; vendor/bin/rr get --no-config -q -n</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">COPY</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> --chown=appuser:appuser ./ ./</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">RUN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> composer du --classmap-authoritative</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">CMD</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"./rr"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"serve"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>The following happens in it:</p>
<ol>
<li>Install all necessary system packages using <code>apt</code>, <code>docker-php-ext</code>, and <code>pecl</code>, enable PHP extensions.
<ol>
<li>Note: I install xDebug, but <strong>do not enable</strong> it. I'll enable and configure it with a separate config file a bit later. I do this so that it doesn't work in production under any circumstances, including carelessness.</li>
</ol>
</li>
<li>Install <code>composer</code></li>
<li>Add the PHP config for production</li>
<li>Create a user under which the application will run. This is important because files will be created and modified inside the container (composer packages, logs, results of changes from phpcsfixer, etc.), and we need the user from the host system (ourselves) to be able to read and edit these files. We accept the user and group id values as arguments so that we can set them differently for different systems.</li>
<li>Separately add the <code>composer.json</code>, <code>composer.lock</code>, and <code>configuration.php</code> files (config for <code>yiisoft/config</code>) to the container, and install dependencies using <code>composer install</code>. And immediately download the RoadRunner binary. We do this step before copying the project files, as Docker will cache the result and won't repeat these actions until the two source files change, i.e., as long as the list of dependencies and their versions remains unchanged.</li>
<li>Add all project files</li>
<li>Execute <code>composer du --classmap-authoritative</code>. This step serves two purposes:
<ol>
<li>Obvious: after adding project files, we <a href="https://getcomposer.org/doc/articles/autoloader-optimization.md#optimization-level-2-a-authoritative-class-maps" target="_blank" rel="noopener noreferrer">optimize the autoloader's speed</a>.</li>
<li>Non-obvious: the merge map for <a href="https://github.com/yiisoft/config/blob/master/README.md#how-it-works" target="_blank" rel="noopener noreferrer">yiisoft/config</a> is generated by the <code>dump-autoload</code> command hook.</li>
</ol>
</li>
</ol>
<p>If you have any questions about its contents - I'm always happy to answer them <a href="https://t.me/viktorprogger_channel_ru" target="_blank" rel="noopener noreferrer">in my Telegram channel</a>.</p>
<h2>Docker compose config</h2>
<p>Secondly, there's always a <code>docker-compose.yml</code> file in the project repository. Yes, I should rename it to <code>compose.yaml</code> under the new standard, but I keep forgetting 😅 Here's its approximate must-have content:</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"3.8"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">services</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  php</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    depends_on</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      db</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        condition</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">service_healthy</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      dockerfile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.docker/php/Dockerfile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      context</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      args</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        USER_ID</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${USER_ID:-1000}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        GROUP_ID</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${GROUP_ID:-1000}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    command</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"./rr serve -c .rr.dev.yaml"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${USER_ID:-1000}:${GROUP_ID:-1000}"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    restart</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">unless-stopped</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    ports</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${WEB_PORT:-80}:80</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    volumes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./.docker/php/php.ini:/usr/local/etc/php/conf.d/40-custom.ini:ro</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./.docker/php/xdebug.ini:/usr/local/etc/php/conf.d/99-xdebug.ini:ro</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./.docker/data/composer:/home/appuser/.composer</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./:/var/www</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    environment</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      TZ</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">Asia/Almaty</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      PHP_IDE_CONFIG</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${PHP_IDE_CONFIG:-}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      DB_NAME</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${DB_NAME}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      DB_HOST</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${DB_HOST}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      DB_PORT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${DB_PORT}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      DB_LOGIN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${DB_LOGIN}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      DB_PASSWORD</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${DB_PASSWORD}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      XDEBUG_MODE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${XDEBUG_MODE:-off}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      XDEBUG_CONFIG</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"client_host=host.docker.internal"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      XDEBUG_TRIGGER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"yes"</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  db</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    image</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">postgres:15-alpine</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    restart</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">unless-stopped</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    volumes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.docker/data/postgres/db:/var/lib/postgresql/data</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    environment</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      POSTGRES_PASSWORD</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${DB_PASSWORD:-dev}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      POSTGRES_USER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${DB_USER:-dev}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      POSTGRES_DB</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${DB_NAME:-dev}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    ports</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${DB_PORT:-5432}:5432"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    healthcheck</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      test</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: [</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"CMD-SHELL"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"pg_isready -U ${DB_USER:-dev}"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      interval</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">3s</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      timeout</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">3s</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      retries</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">10</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>From this example, you can see that I actively use environment variables. Their values are set in the <code>.env</code> file, which is added to .gitignore. Instead of it, I commit .env.example to the repository, which contains all possible environment variables with empty or fake values. Thanks to this approach, the application depends on environment variables, not on the ways to add them. And here's an example of my <code>.env.example</code>:</p>
<div class="language-env line-numbers-mode" data-highlighter="shiki" data-ext="env" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-env"><span class="line"><span>##################################</span></span>
<span class="line"><span># Port forwarding</span></span>
<span class="line"><span>##################################</span></span>
<span class="line"><span>WEB_PORT=80</span></span>
<span class="line"><span></span></span>
<span class="line"><span>##################################</span></span>
<span class="line"><span># xDebug settings</span></span>
<span class="line"><span>##################################</span></span>
<span class="line"><span># Uncomment next line for xDebug usage with PhpStorm</span></span>
<span class="line"><span># PHP_IDE_CONFIG=serverName=docker</span></span>
<span class="line"><span># XDEBUG_MODE=develop,debug</span></span>
<span class="line"><span></span></span>
<span class="line"><span># LINUX ZONE. Next configs are for xDebug in linux environment only.</span></span>
<span class="line"><span># COMPOSE_FILE=docker-compose.yml:docker-compose.linux.yml</span></span>
<span class="line"><span># Uncomment HOST_IP and set host ip in the docker network when using linux, look at docker-compose.linux.yml</span></span>
<span class="line"><span># Although it usually is 172.17.0.1, your real value can be easily found with command `ip address | grep docker`,</span></span>
<span class="line"><span># you'll see it like "inet 172.17.0.1/16 ..."</span></span>
<span class="line"><span># HOST_IP=172.17.0.1</span></span>
<span class="line"><span></span></span>
<span class="line"><span>##################################</span></span>
<span class="line"><span># Database settings</span></span>
<span class="line"><span>##################################</span></span>
<span class="line"><span>DB_HOST=db</span></span>
<span class="line"><span>DB_PORT=5432</span></span>
<span class="line"><span>DB_NAME=dev</span></span>
<span class="line"><span>DB_LOGIN=dev</span></span>
<span class="line"><span>DB_PASSWORD=dev</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>If you don't know why certain xDebug settings are needed - I recommend reading my <a href="/posts/xdebug-docker-config-example.html" target="_blank">example of configuring xDebug in Docker</a>.</p>
<h2>Peculiarities of development under Linux</h2>
<p>If you're developing on Linux and not using Docker Desktop (which is exactly what I do), then the <code>host.docker.internal</code> host is not available inside containers. And to be able to use xDebug (it needs to connect to the IDE from the container independently), I always add this block to <code>docker-compose.yml</code> in the service with PHP:</p>
<div class="language-yaml line-numbers-mode" data-highlighter="shiki" data-ext="yaml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yaml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">extra_hosts</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">:</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">host.docker.internal:${HOST_IP:-172.17.0.1}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>And when I plan to work on a project not alone, I extract this block into a neighboring file, which I usually call <code>docker-compose.linux.yml</code>, while everything else remains in the main file. And to avoid writing the list of connected configs each time in every command (for example, <code>docker compose -f docker-compose.yml -f docker-compose.linux.yml run --rm php php -v</code>), I also add this environment variable to <code>.env</code>:</p>
<div class="language-env line-numbers-mode" data-highlighter="shiki" data-ext="env" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-env"><span class="line"><span>COMPOSE_FILE=docker-compose.yml:docker-compose.linux.yml</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><h2>Launch</h2>
<p>First of all, of course, we add <code>composer.json</code> and other project files. And the actual launch of such an environment is simple to the point of banality:</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">docker</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> compose</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> up</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> -d</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>During the first launch, the container will first build. This can take more than 5 minutes depending on the machine's power and the quality of the internet connection. And when it finally starts - everything is ready for work and debugging.</p>
<div class="hint-container tip">
<p class="hint-container-title">Tips</p>
<p>A small advice: to be sure that the project will start in production, add/update php dependencies also through this environment, not with locally installed <code>composer</code>:</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">docker</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> composer</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> run</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --rm</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> --no-deps</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> php</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> composer</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> require</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> foo/bar</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>If some package has a dependency on the environment (for example, the PHP <code>pcntl</code> extension is needed, which you decided not to install in your <code>Dockerfile</code>), you'll immediately see what the problem is. This will also help install exactly those versions of packages that are compatible with your environment, if such are available.</p>
</div>
<h2>Conclusion</h2>
<p>Creating an effective dev environment is not just about setting up tools, it's an evolutionary process. My approach, which I described in this article, has formed over years of working on various projects, from pet projects to large team developments.</p>
<p>The key idea I would like to emphasize is the balance between standardization and flexibility. On one hand, we strive for a reproducible environment that's easy to deploy on any machine. On the other hand, we need the ability to quickly adapt the configuration to the specifics of a particular project.</p>
<p>The use of Docker, a carefully thought-out structure of the <code>.docker</code> directory, flexible settings through environment variables - all this allows us to achieve this balance. We get a stable environment that is easy to customize at the same time.</p>
<p>I paid special attention to performance optimization (data caching, proper layer construction in <code>Dockerfile</code>, autoloader optimization) and convenience. These aspects are often overlooked, especially in the early stages of a project, but they are critically important for long-term success.</p>
<p>I hope that my experience and approach described in this article will help you not only set up environments for your projects faster but also rethink the process of organizing development itself. Remember that each project is unique, and don't be afraid to experiment and adapt these practices to your needs.</p>
<p>Today, I've covered the basics without delving too deep. Many topics have been touched upon here, each of which could be explored in great depth. Let me know what you find interesting, give likes on social media, and I'll publish more content on such topics. You can reach out to me <a href="https://www.reddit.com/u/viktorprogger" target="_blank" rel="noopener noreferrer">on Reddit</a> or <a href="https://x.com/viktorprogger" target="_blank" rel="noopener noreferrer">in X</a>. Let's make the development process more efficient and enjoyable together!</p>
]]></content>
    <category term="Development Techniques"/>
    <published>2024-09-30T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">Don&amp;apos;t do this: nonexistent trait fields usage</title>
    <id>https://viktorprogger.name/posts/dont-do-this-non-existent-trait-fields.html</id>
    <link href="https://viktorprogger.name/posts/dont-do-this-non-existent-trait-fields.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>With this article, I open a rubric &quot;Don't do this.&quot; It will contain code from real projects, from which I will remove all references to the projects themselves. And, of course, my explanations of why shouldn't you do this, and the way it
should be done.</p>
<p>So, the first patient. Inside the trait, the fields of its descendants are used. I mean something like this:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">trait</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Foo</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">string</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">field</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> .</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> static</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">::</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">CONSTANT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> .</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">method</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">Warning</p>
<p>Or even worse, <code>$this-&gt;{$attribute}</code>. But it's out of the question, as the chance of getting an error 500 rises to almost 100%.</p>
</div>
]]></summary>
    <content type="html"><![CDATA[<p>With this article, I open a rubric &quot;Don't do this.&quot; It will contain code from real projects, from which I will remove all references to the projects themselves. And, of course, my explanations of why shouldn't you do this, and the way it
should be done.</p>
<p>So, the first patient. Inside the trait, the fields of its descendants are used. I mean something like this:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">trait</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Foo</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">string</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">field</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> .</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> static</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">::</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">CONSTANT</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> .</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">method</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">Warning</p>
<p>Or even worse, <code>$this-&gt;{$attribute}</code>. But it's out of the question, as the chance of getting an error 500 rises to almost 100%.</p>
</div>
<!-- more -->
<h2>What's wrong here?</h2>
<p>Technically, a class that uses this trait is not required to declare these field, constant and method. <em>They may not be there</em>. At the same time, you can even deceive the static analyzer if you add the following comment to the trait:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">/**</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  * @mixin SomeClass</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  */</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Of course, this will work only if <code>SomeClass</code> contains them all: the <code>$field</code>, the <code>CONSTANT</code> and the <code>method()</code>.</p>
<p>The problem arises when this trait is added to another class and the developer does not know or forgets to declare all this stuff.</p>
<div class="hint-container note">
<p class="hint-container-title">Note</p>
<p>As a rule, traits of such a quality contain lots of code: 500-1000 lines or even more. It becomes quite
difficult to find out such errors. And given that the project is unlikely to be covered by tests, the problem may well reach production environment and cause problems for the end users.</p>
</div>
<h2>What is the right way?</h2>
<p>One should use abstract methods:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">trait</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Foo</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">string</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getField</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getConstant</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">method</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    abstract</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> protected</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getField</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    abstract</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> protected</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getConstant</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    abstract</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> protected</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> method</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Thus, we have a technical limitation at the language level: any class that includes this trait will also be <strong>obliged</strong> to declare three methods on which the main functionality of the trait depends.</p>
<div class="hint-container tip">
<p class="hint-container-title">Tips</p>
<p>Just do not forget to add detailed comments to the methods about what values it should return and what exceptions should be thrown in which case.</p>
</div>
<h2>Correct option 2</h2>
<p><em>For adherents of the ideas &quot;Traits are evil&quot; and &quot;Composition is strength&quot;</em>:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BarInterface</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getField</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">():</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getConstant</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">():</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> method</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">():</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> string</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">readonly</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> final</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Foo</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">BarInterface</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">string</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getField</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getConstant</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() </span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">method</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Here we replace the trait with two things:</p>
<ul>
<li>Interface with the three methods which were <code>abstract</code> and <code>protected</code> in the trait.</li>
<li>The class to which we pass the implementation of this interface in order to call these methods and use their execution results.</li>
</ul>
<div class="hint-container tip">
<p class="hint-container-title">Tips</p>
<p>The result seems to be the same, but there is a nuance. Now the classes that implement <code>BarInterface</code> are not linked by common code.
This reduces the code coupling and increases its cohesion.
Further elaboration and refactoring of a such code will be more simple and comfortable.</p>
</div>
]]></content>
    <category term="Dont do this"/>
    <published>2023-04-23T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">Elusive 502, or How did Senior Developers Couldn&amp;apos;t Find an Error</title>
    <id>https://viktorprogger.name/posts/elusive-502-gateway-timeout.html</id>
    <link href="https://viktorprogger.name/posts/elusive-502-gateway-timeout.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>What are the worst mistakes in programming? I would single out two types: </p>
<ul>
<li>those that cause a business to lose a lot of money</li>
<li>and those that are the least common. </li>
</ul>
<p>While the first ones are immediately clear, what about the second ones? The fact is that the less often we encounter some type of error, the more difficult it is to understand what caused them.</p>
<p>This happened to me at work as well. One morning, the testers noticed that some requests to the backend returned a 502 (Gateway Timeout) error. This bug was a release stopper, so all the senior developers and a devops engineer took it up. At first, it was believed that Nginx returned this error, and the backend had nothing to do with it. After a while, we realized that PHP was to blame. We sinned for everything: turned off the BlackFire, changed OpCache settings, tried different patch versions in PHP, and so on. However, the error itself was not in the infrastructure, but directly in the application code.</p>
<img src="/http-502-bad-gateway.jpg" style="width: 100%" alt="Ошибка 502 Bad Gateway">
]]></summary>
    <content type="html"><![CDATA[<p>What are the worst mistakes in programming? I would single out two types: </p>
<ul>
<li>those that cause a business to lose a lot of money</li>
<li>and those that are the least common. </li>
</ul>
<p>While the first ones are immediately clear, what about the second ones? The fact is that the less often we encounter some type of error, the more difficult it is to understand what caused them.</p>
<p>This happened to me at work as well. One morning, the testers noticed that some requests to the backend returned a 502 (Gateway Timeout) error. This bug was a release stopper, so all the senior developers and a devops engineer took it up. At first, it was believed that Nginx returned this error, and the backend had nothing to do with it. After a while, we realized that PHP was to blame. We sinned for everything: turned off the BlackFire, changed OpCache settings, tried different patch versions in PHP, and so on. However, the error itself was not in the infrastructure, but directly in the application code.</p>
<img src="/http-502-bad-gateway.jpg" style="width: 100%" alt="Ошибка 502 Bad Gateway">
<!-- more -->
<h3>Foreword</h3>
<p>As an introduction, I’ll tell you that the application is based on the yii2 framework and is originally written by not the most intelligent developers. For example, the DI container was not used, instead there were components, as it was originally intended in the framework many years ago. When the project took off, specialists of a higher level came to it, and they began to write more architecturally verified code. Because of one such change, we got an error that looked like a 502 on the front-end, and for which there were no records in any logs. The first thing that came to mind: this situation with the logs occurs due to memory overflow. That is, when the php process uses more memory than it is allocated by the system. We are familiar with this situation and know how to recognize it, that is why we checked it right away. But we didn't hit this time.</p>
<p>In some time we turned out, that our problem was of a similar nature: the process fell off due to reaching another system limitation, not memory. In a very non-obvious place, we reached the limit on the maximum nesting of function calls. In other words, we have entered a recursion. Moreover, this recursion was implicit. That is, the function did not call itself, but another function, which in turn called the first function, and that again the second, and that again the first one...</p>
<p>And we came to this as follows. In the configuration, we had a component (let's call it 'foo' for clarity). The configuration of this component looked like this:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'foo'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> => [</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    'class'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> => </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">FooClass</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">::</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    'property1'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> => </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'value1'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>One of our developers added the definition of this class to the DI container:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">FooClass</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">::</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> => </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">static</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> fn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() => </span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Yii</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">::</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$app</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">get</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'foo'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">),</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>What did we get as a result? When the project code accessed a component, the framework tried to create that component. Seeing the need to get a class object in the component definition, it looked for this class first in the DI container, where it found the definition <code>Yii::$app-&gt;get('foo')</code>, and again tried to get the component. And so on in a circle.</p>
<p>This is how the simplest mistake managed to fool developers with more than 10 years of experience. We just don't see it very often.</p>
]]></content>
    <category term="Personal"/>
    <published>2022-09-14T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">Has IT-bubble burst?</title>
    <id>https://viktorprogger.name/posts/has-it-bubble-burst.html</id>
    <link href="https://viktorprogger.name/posts/has-it-bubble-burst.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>A year ago, when I changed jobs, I didn’t even publish my resume. I acted like this from previous experience: every time opening a resume to the public, I had to communicate a lot with companies with which we obviously could not succeed.  So I asked my friends what companies are currently looking for developers, knocked on a couple of doors and found a great place. Later, however, it turned out that we did not quite fit each other with this company, and I quit.  I am sure that if I opened my resume, I would again, like a couple of years before, have to dig myself out of the responses of HRs. For me personally, things have changed a lot since then.</p>
<img src="/bubble.jpg" style="width: 100%" alt="IT-bubble">
]]></summary>
    <content type="html"><![CDATA[<p>A year ago, when I changed jobs, I didn’t even publish my resume. I acted like this from previous experience: every time opening a resume to the public, I had to communicate a lot with companies with which we obviously could not succeed.  So I asked my friends what companies are currently looking for developers, knocked on a couple of doors and found a great place. Later, however, it turned out that we did not quite fit each other with this company, and I quit.  I am sure that if I opened my resume, I would again, like a couple of years before, have to dig myself out of the responses of HRs. For me personally, things have changed a lot since then.</p>
<img src="/bubble.jpg" style="width: 100%" alt="IT-bubble">
<!-- more -->
<div class="hint-container note">
<p class="hint-container-title">Disclaimer</p>
<p>The article was written at the end of December, when I did not yet know what a stupid thing I did when publishing a resume. The conclusion made can be found <a href="#post-scriptum-and-the-sudden-twist">at the end of the article</a>. I publish it only now, because I had no time to polish the text for a long time.</p>
</div>
<h2>Unexpectedly long search</h2>
<p>My salary expectations remained at the same level, but after the publication of my resume on HeadHunter, not a single company wrote to me. Not a single response. I, to put it mildly, was surprised, and began to write responses myself.  At the same time, I published my resume on other job boards. Not all companies, to whom I wrote, sent at least some kind of answer, some of them refused silently. With some, I had a dialogue for a while. I went through several interviews at all stages, but I was refused everywhere.</p>
<p>Only one company made a kind of gift for the new year in the form of an offer 😃 I went out with them after the New Year holidays in the Russian Federation.</p>
<p>At the same time, I improve my English in order to calmly communicate with potential employers from Europe and America. I suppose that I could try now, but it’s too scary: after several years of lack of practice in English, my level fell somewhere to B1.</p>
<p>Well, until I went out to a new employer, I decided to focus on project work, freelancing, and consulting other companies. I have quite a lot of experience: I not only wrote the code, but also participated in the formation and change of the processes of the IT departments of the companies in which I worked. I watched a lot of how well or poorly certain processes work in more advanced IT companies.</p>
<div class="hint-container info">
<p class="hint-container-title">Question</p>
<p>And here a question arises for the audience: do you get the impression that the IT bubble has burst, and it has become more difficult to find a job?</p>
</div>
<h2>Post Scriptum and the sudden twist</h2>
<p>When I already found a job, hh.ru finally showed me a notification that <em>only companies in Kazakhstan see my resume</em>: after all, I honestly indicated Almaty in it. At the same time, I indicated the salary in RUB. With the ruble exchange rate, which has grown 1.5 times since the spring of 2022, this salary in terms of KZT was inadequately high, and I could not interest Kazakh companies. And the Russian ones just did not see my resume. In your opinion, was this the only problem I had, and is everything in order with hiring in IT and PHP, or has our bubble deflated? And is it a bubble at all? Let's <a href="https://t.me/viktorprogger_channel_ru" target="_blank" rel="noopener noreferrer">discuss</a>.</p>
]]></content>
    <category term="Personal"/>
    <published>2023-01-10T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">How did I use the Builder design pattern</title>
    <id>https://viktorprogger.name/posts/how-did-i-use-the-builder-design-pattern.html</id>
    <link href="https://viktorprogger.name/posts/how-did-i-use-the-builder-design-pattern.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>There are many architectural patterns in the development world. Some of them we use every day, some - less often. Surely each of you has seen the Singleton and the Factory many times. Many of you have written them on your own. But when I first read about <a href="https://refactoring.guru/design-patterns/builder" target="_blank" rel="noopener noreferrer">the Builder pattern</a>, at first I did not understand in what situation it can be applied. What is completely ridiculous: I regularly used its implementation (QueryBuilder from the Yii framework), but my eye was so blurry that I could not match the name and functionality of this class and the corresponding design pattern 😂 Of course, after a while it dawned on me. And over more time, a situation was found in which the Builder pattern fit perfectly.</p>
<img style="margin: auto; display: flex" src="/builder-en.png" alt="The Builder design pattern">
]]></summary>
    <content type="html"><![CDATA[<p>There are many architectural patterns in the development world. Some of them we use every day, some - less often. Surely each of you has seen the Singleton and the Factory many times. Many of you have written them on your own. But when I first read about <a href="https://refactoring.guru/design-patterns/builder" target="_blank" rel="noopener noreferrer">the Builder pattern</a>, at first I did not understand in what situation it can be applied. What is completely ridiculous: I regularly used its implementation (QueryBuilder from the Yii framework), but my eye was so blurry that I could not match the name and functionality of this class and the corresponding design pattern 😂 Of course, after a while it dawned on me. And over more time, a situation was found in which the Builder pattern fit perfectly.</p>
<img style="margin: auto; display: flex" src="/builder-en.png" alt="The Builder design pattern">
<!-- more -->
<p>At the moment this has happened only once. Usually I worked on adult projects, where either such problems had already been solved, or it was a terrible legacy without resources for refactoring. So, where did I need the Builder?</p>
<h3>Task: product feeds optimization</h3>
<p>The project was an online store and a marketplace at the same time. A dozen commodity feeds were generated on it. These were files of various formats with a list of products, their categories and characteristics. And they were uploaded to other marketplaces or, more often, to various platforms that provide advertising and analytics services. Each feed had its own settings: what products/brands/categories/etc. should be included in it, what data should be available for products, etc. The settings were stored in the database and displayed in the UI by our marketers.</p>
<p>Each feed was generated independently of the others. Initially, it took up to 10 minutes for each feed and heavily loaded the system, because it read data from the database very actively. And when it was necessary to generate all the feeds, this procedure stretched for an hour and a half, during which the reading replica of the database worked at the limit.</p>
<h3>First iteration of optimization</h3>
<p>First of all, I've done two things:</p>
<ul>
<li>I've used SphinxSearch to search for products and get some data on them. The initial version of feeds was written even before the introduction of the Sphinx, and it was difficult to introduce something new into it, so hands have reached it only now.</li>
<li>I've made feed generation a single process. The data could be obtained once, and based on it, the necessary feeds could be built. The feed was divided into two things: a filter that chose the suitable products, and a template that created a file from these products. We took a product and passed it through all the feeds. If the filter passed it, the feed wrote information about this product into a file. And so we did for each product.</li>
</ul>
<div class="hint-container info">
<p class="hint-container-title">Info</p>
<p>This approach brought the time of creating feeds into a constant: about 6 minutes regardless of their number. However, it could have been better: in some feeds, only 20-25% of the products were used, but even when they were launched separately, all products were still run through the filter.</p>
</div>
<h3>Second iteration: The Builder</h3>
<p>The second thing that came to my mind was to create my own QueryBuilder with top-level “queries”: <code>withBrands()</code>, <code>withCategories()</code>, <code>withPhotoType()</code>, etc. When it was necessary to generate several feeds at a time, a QueryBuilder instance was taken for each of them, and they were merged into one to select from the database only those products that are needed by the current set of feeds. The two goals have been achieved:</p>
<ul>
<li>The independence of the entities in the feed generation module from the database structure was preserved.</li>
<li>Feed generation speed was significantly improved for sertan cases.</li>
</ul>
<p>As a result the generation code itself changed only in one place: getting products from the repository changed from <code>getProducts(): iterable</code> to <code>getProducts(QueryBuilder …$queries): iterable</code></p>
]]></content>
    <category term="Personal"/>
    <published>2022-07-18T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">How to choose an infrastructure tool</title>
    <id>https://viktorprogger.name/posts/how-to-choose-an-infrastructure-tool.html</id>
    <link href="https://viktorprogger.name/posts/how-to-choose-an-infrastructure-tool.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>When an IT product evolves, there always comes a time when it becomes necessary to bring some new tool into it or replace the old one with something more suitable. While it seems that a lot of articles have been written on the topic of choosing tools, and there is nothing new to say, the question still remains relevant, in some places even sharp.</p>
<img src="/choice.jpg" style="width: 100%" alt="How to choose an infrastructure tool">
<p>Here are the most popular approaches:</p>
<ul>
<li>Find something hype on the left road</li>
<li>Take something proven and reliable on the right road</li>
<li>Go straight ahead and use your favorite tool</li>
</ul>
<p>They are good because you don't have to think when using them. You just take what you want - and that's it 😃 Well, if there is a need or a desire to approach the issue more seriously - welcome under the cut, we'll figure it out.</p>
]]></summary>
    <content type="html"><![CDATA[<p>When an IT product evolves, there always comes a time when it becomes necessary to bring some new tool into it or replace the old one with something more suitable. While it seems that a lot of articles have been written on the topic of choosing tools, and there is nothing new to say, the question still remains relevant, in some places even sharp.</p>
<img src="/choice.jpg" style="width: 100%" alt="How to choose an infrastructure tool">
<p>Here are the most popular approaches:</p>
<ul>
<li>Find something hype on the left road</li>
<li>Take something proven and reliable on the right road</li>
<li>Go straight ahead and use your favorite tool</li>
</ul>
<p>They are good because you don't have to think when using them. You just take what you want - and that's it 😃 Well, if there is a need or a desire to approach the issue more seriously - welcome under the cut, we'll figure it out.</p>
<!-- more -->
<p>So, if not “quickly”, how to choose then?</p>
<h3>1. Make a list of requirements</h3>
<p>The very first and most obvious step. But not everything is as simple as we would like. The list should be relevant right now and for the foreseeable future. That is, you need to strike a balance between two directions:</p>
<ul>
<li>Do not look into the distant future with the potential growth of the company to a “MAANG killer” and increase RPS from the current 0.1 to 1000.</li>
<li>Do not leave behind real requirements, current ones and from the foreseeable future (say, in 1-3 years).</li>
</ul>
<h3>2. Choose several candidates that meet all the requirements</h3>
<p>And when this is done - conduct a comparative analysis of them, using the requirements drawn up earlier. Do not forget to take into account a few more points:</p>
<ol>
<li>Ease of use.<br>
The tool that you are going to use regularly should not interfere with your use of it, and even better, it should make this process pleasant and convenient.</li>
<li>Frequency of updates.<br>
Some old, reliable tools eventually stagnate and stop being updated. In this case, even if such a tool seems to be the ideal solution now, there is a rather high probability that it will have to be changed again soon, as it will either no longer meet the security requirements, or will not implement the new features that its rapidly developing competitors will have. There are young tools on the other hand. It is possible to stagnate for them when there is too small community and a few maintainers.</li>
<li>The presence of a community, integrations, plugins.<br>
The lack of a large community for a new cool tool and a large number of integrations that its more proven counterparts have can be a very serious obstacle in the development of your company. Not being able to quickly embed the tool into your current processes is a tangible problem. With older, slower, less user-friendly tools, you can often save yourself dozens or even hundreds of hours of work simply because your problems have already been solved by someone else. And no matter how cool a tool is, at the dawn of its development there is always one obstacle: users need to solve their problems on their own.</li>
<li>The presence of expertise in the team.<br>
Even if an unfamiliar tool does not look complicated, and installation and configuration guides fit on one screen, this does not mean that you will not have difficulties in a month. And since you have no expertise in working with this tool, it is not known how long it will take you to eliminate errors or optimize its work.</li>
</ol>
<p>In the general case, the analysis should take into account only the main functionality common to all systems. I'm talking about the fact that all infrastructure tools, without exception, sooner or later become obsolete. Immediately think about how you will change this tool to some other one. Not to some specific, but to an abstract one. Caching system - to another caching system. In the case of systems for which there are no standards like PSR, for this you will have to conduct a deeper analysis yourself to identify this common functionality.</p>
<div class="hint-container tip">
<p class="hint-container-title"> </p>
<p>If we are talking about a caching system, then you can be guided by the current PSR. At the same time, the caching system itself can provide much more features, as, for example, Redis does. I do not recommend using these features in code directly. Of course, Redis can be used not only as a caching system, but also as a database. This is fine. But using it as a caching system that uses database functions that are not provided by PSR will sooner or later lead to problems.</p>
</div>
<h3>3. Weigh all the pros and cons of the chosen instruments</h3>
<p>Even in similar situations in similar companies, the choice can be radically different, since the value of one or another point will also change. For some, the ease of use of the system is more important, for some it is the size of the community and related tools, and for some this tool will be built into such a fast-growing business that bandwidth will become the main factor for it, and not at all ease of use and speed its embedding. You can create a table with weights for each tool feature, if you want to make this comparison more visual.</p>
<h3>Real Life Example</h3>
<p>Now let's try to choose some real tool, which we will select according to the scheme above. The company where I work recently faced the need to change the queue server. Before the replacement, Redis was used, but it ceased to suit us. The volume of information passed through it is growing, its value is high, and we are not ready to lose it in the event of a failure. This implies the <em>main criterion by which it does not suit us: the reliability of message delivery</em>. We do not deal with routing messages between queues, the complex logic of their delivery, and other similar things. The growth of the traffic passed through the queues increases by a maximum of two times per year. Another criterion: the speed of launching a new message broker in production. We want to finish embedding in a month. So, some completely new tool is not suitable, because we are not ready to spend extra time on its embedding because of the lack of finished cases.</p>
<p>At the stage of choosing a tool, everything came down to a classic confrontation:</p>
<ul>
<li><strong>RabbitMQ</strong>. A couple of our developers already had experience with it. It is also a fairly popular queue server that has a rich set of functionality and works according to the amqp standard. It is the most well documented and easier to install and maintain than other brokers that implement amqp.</li>
<li><strong>Apache Kafka</strong> - very popular among enterprise-level companies. Works according to its own standard.</li>
<li><strong>NATS</strong> was considered as one of the options for a long time, because it did not immediately become clear that it does not persist messages, which means that it loses them after it is turned off. And it was also against him that it only receives messages via UDP, which adds risks of their loss.</li>
</ul>
<div class="hint-container info">
<p class="hint-container-title">Disclaimer</p>
<p>A detailed analysis of Kafka and RabbitMQ is beyond the scope of this article, there are plenty of similar materials on the Internet. I will focus solely on the choice according to our own criteria.</p>
</div>
<p>So, let's evaluate the two remaining competitors in terms of their capabilities and shortcomings.</p>
<h4>RabbitMQ</h4>
<ul>
<li>Some people on our team have already worked with it, so it has a head start.</li>
<li>There is a possibility of flexible message routing, thanks to the amqp protocol. The chip is cool, but it is not taken into account in the rating, because this cannot be reused when switching to another broker. Their use will make it more difficult to switch to another instrument when the moment is right.</li>
<li>Method of clustering. RabbitMQ uses database synchronization for clustering and high availability. It works quickly, clearly, but does not forgive network errors. In other words, if the broker's nodes are located on different servers, in different data centers, network problems will inevitably arise, which will lead to data desynchronization in the nodes.<div class="hint-container info">
<p class="hint-container-title">Shovel</p>
<p>There is also an alternative in the form of the Shovel plugin, which uses the amqp standard under the hood and simply forwards messages from the queue of one server to the exchanger of another, while correctly handling network errors, but adding significant overhead, because this is not just a copy of raw data, but a complete processing of a message.</p>
</div>
</li>
<li>Message delivery guarantees depend on the settings and only two types are supported:
<ul>
<li>at least once, any message will be delivered one or more times</li>
<li>at most once, any message will not be delivered more then once (but it also may be not delivered at all)</li>
</ul>
</li>
<li>It is possible to enable writing messages to disk so that they are not lost when the queue broker is restarted.</li>
<li>Rabbit's throughput is not bad, but it seriously drops when writing messages to disk is turned on, down to several thousand messages per second. Other brokers have orders of magnitude higher throughput (from hundreds of thousands to millions per second), but we are within these numbers in the foreseeable future.</li>
</ul>
<h4>Apache Kafka</h4>
<ul>
<li>We've heard a lot about it, but haven't seen it live.</li>
<li>Implements only pub/sub, does not know how to route messages. Which, again, is not a minus, but a statement of fact.</li>
<li>Ability to cluster - available out of the box, without a noticeable loss of performance and restrictions on the local network, which allows you to make readonly replicas, store different queue shards on different servers, etc. Minus: another tool is required, ZooKeeper.</li>
<li>Message delivery guarantees also depend on the settings, but all three possible types are supported, including “exactly once”, a guarantee that a message will be delivered to the recipient exactly once. However, with this mode of delivery guarantees, there are difficulties when setting up Kafka clusters. Of course, the corresponding recipes are already on the Internet.</li>
<li>Writing messages to disk is not disabled due to architectural features: queues in Kafka are logs where messages are added by producers and from where they are read by consumers. In other words, all messages received by Kafka are always available for reading, including those that have been processed a long time ago.</li>
<li>Bandwidth - about 1 million messages per second.</li>
</ul>
<p>When comparing the pros and cons of these two tools, it becomes clear that Kafka wins tangibly. <em>And yet, we made a choice in favor of RabbitMQ.</em> It's all about its very first plus: this system is already familiar to us, and it is less likely that we will encounter problems in production. At the same time, we do not plan to make a geographically distributed system, i.e. features of Rabbit clustering in our case are not a minus.</p>
<p>In Kafka, however, its architectural difference brings significant difficulties. First, Kafka does not actually have a queue: he has logs divided into partitions, and exactly one consumer can read from each partition. We have never worked with such a scheme, and there is a high probability of a shot in the foot when working with real data in production. We especially feel apprehensive at the thought of scaling when processing messages. You can simply add consumers to a Rabbit. And with Kafka, it is necessary to think in advance about the number of partitions and the distribution of messages over them in order to preserve the order of tasks in some cases.</p>
<p>Our company is growing and developing, and we do not renounce the possibility that we will change RabbitMQ to Kafka in a couple of years, if suddenly our requirements for the queue broker change. And in order for this replacement to be painless, we will not get hung up on the features of the current tool.</p>
<h3>Conclusions</h3>
<p>If you follow the above plan, you can choose any tool from the available options. Of course, it leaves room for risk: if you choose unequivocally the best available, but unfamiliar tool, you can stumble upon pitfalls during operation. These risks should also be taken into account when choosing a tool. I will give a plan for preparing for the choice of an infrastructure tool again:</p>
<ol>
<li>Make a list of requirements, not missing the real ones and not inventing something beyond what is necessary.</li>
<li>Select several candidates and conduct their comparative analysis.</li>
<li>Evaluate winning and losing positions for different instruments, guided by current priorities.</li>
</ol>
<p>Tell me, have you followed a similar plan in your practice? What oddities happened due to unaccounted for requirements or features of the tool? I’m waiting for you <a href="https://t.me/viktorprogger_channel_ru" target="_blank" rel="noopener noreferrer">in the telegram chat</a>, I’ll be glad to hear your stories and to chat with you!</p>
]]></content>
    <category term="Development process"/>
    <published>2022-08-08T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">Forbidden topic or How to deal with procrastination</title>
    <id>https://viktorprogger.name/posts/how-to-deal-with-procratination.html</id>
    <link href="https://viktorprogger.name/posts/how-to-deal-with-procratination.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>There is such a topic among us, IT people, which is usually avoided. The fact is that we are not able to work productively all day long. On the one hand, this is no secret to anyone. On the other hand, it is not customary to discuss it, and for some reason we think that no one but ourselves is in the know. In an ideal world, a programmer with good experience, thirty or forty years old, is quite capable of working a couple of hours a day with great efficiency, devoting the rest of the time to all sorts of routines: code reviews, communication with colleagues, task analysis, and so on. In the real world, procrastination lurks around every corner. No wonder one of the most popular memes in IT right now are memes about burnout.</p>
<img style="margin: auto; display: flex" src="/procrastination_en.png" alt="Burnout">
]]></summary>
    <content type="html"><![CDATA[<p>There is such a topic among us, IT people, which is usually avoided. The fact is that we are not able to work productively all day long. On the one hand, this is no secret to anyone. On the other hand, it is not customary to discuss it, and for some reason we think that no one but ourselves is in the know. In an ideal world, a programmer with good experience, thirty or forty years old, is quite capable of working a couple of hours a day with great efficiency, devoting the rest of the time to all sorts of routines: code reviews, communication with colleagues, task analysis, and so on. In the real world, procrastination lurks around every corner. No wonder one of the most popular memes in IT right now are memes about burnout.</p>
<img style="margin: auto; display: flex" src="/procrastination_en.png" alt="Burnout">
<!-- more -->
<h2>Introduction</h2>
<p>The problem lies precisely in the fact that it is not customary to talk about it. With an 8-hour work day, most developers sincerely believe that they must be as productive as possible for all 8 hours. Or they are sure that their superiors think so. As a result, we either really try to work much more productively than we can, or we diligently pretend that we are.</p>
<div class="hint-container tip">
<p class="hint-container-title">An anecdote</p>
<p>If a programmer quickly answers in a chat, it means that he is currently busy with work.</p>
</div>
<p>The taboo nature of this topic leads to an almost complete absence of materials about it in the public domain, which is why we have to get out of the hole we dug ourselves. And if it doesn’t work out, that burnout follows. Are you familiar with this?</p>
<ol>
<li>I procrastinate and can't do anything</li>
<li>I'm angry at myself for not being able to do as much as I want, or resenting uninteresting tasks/incorrect guidance/outdated tools.</li>
<li>Performance is declining, self-flagellation is rising</li>
<li>Complete burnout</li>
</ol>
<p>Yes, a lot of articles about burnout tell how to prevent it. But at the same time, almost nowhere is it said about procrastination, although it comes long before burnout. And the funny thing is that articles about burnout are read by those who have already fallen into it. As a rule, in the first few years of work in the profession, a couple of burnouts happen to most of us. Of course, we get a wealth of experience as a result, thanks to which it becomes easier to avoid such crises next time. But, nevertheless, rarely anyone manages to avoid them completely. And even more so - few people think of sharing their experience with younger colleagues.</p>
<div class="hint-container info">
<p class="hint-container-title">Info</p>
<p>After all, &quot;if I burn out, it's my own problem&quot;. This is an indication that &quot;I’ve failed&quot;. At least that's what society thinks.</p>
</div>
<p>And at the same time, procrastination is something that happens to absolutely all people. Sooner or later, anyway. Usually, we are closely acquainted with procrastination at school. And if at this age it is customary to blame the education system and our teachers, then in the case of a favorite job, everything turns out to be much more complicated. The person is again faced with the feeling of being unable to continue his work, while experiencing a sense of responsibility to himself and the employer, as a result of which he makes the worst possible choice: begins to blame himself.</p>
<blockquote>
<p>Here, I'm late with the deadlines for the submission of the task. After all, I love this job so much, why can't I do something for its benefit?</p>
</blockquote>
<p>I confess, I myself am a sinner. I still haven't been able to get rid of that reflex. And yet, it is nothing more than a reflex. Procrastination is a state in which many people fall into many times during their lives. So, it has certain mechanics. It has determined causes and remedies.</p>
<div class="hint-container tip">
<p class="hint-container-title">Tips</p>
<p>I guess I love my profession, but not this job. Hey HRs, I'm free!</p>
</div>
<h2>How to fight procrastination?</h2>
<p>No way.</p>
<h2>How does procrastination work?</h2>
<p>It is important to understand that this is a defense mechanism. There is no need to fight it, because it's just a signal that something goes wrong. Although there can be an infinite number of local reasons for its occurrence, in general, procrastination is a signal of fatigue. You are tired and cannot continue to work. Fatigue can be any, including for reasons that are not quite obvious: physical, moral, from forbidding yourself to be yourself, etc. Of course, the first thing for you to do is to relax. Go on vacation or devote at least one or two evenings to yourself beloved instead of any “have to”.</p>
<p>But what to do, if after a rest the fatigue does not go away? First of all, check to see if you're really resting while you're resting, or are you continuing to build all sorts of systems in your mind (not necessarily in terms of development) and to think about your “have to”. Or maybe you switch from one job to another? In this case, have a rest. For real. And if even this does not help, try the following options.</p>
<h2>Causes and solutions to procrastination</h2>
<h3>Too have to</h3>
<p>Our mind is designed in such a way that we are always forced to choose between &quot;I want&quot; and &quot;I have to.&quot; And for maximum efficiency, they must be alternated. If you keep postponing your desires until later, after “more important things”, after “have to”, then procrastination and burnout await you. No matter how unnecessary and stupid your desires may seem - allow them to yourself, regularly allow them to yourself. For example, work on the yii3 framework was an outlet for me for a long time. I wrote work tasks as &quot;I have to&quot;, and wrote the framework as &quot;I want&quot;. This also includes computer and sports games, hiking, music, movies, etc.</p>
<h3>Overloaded with plans</h3>
<p>How often have you put off a task until later? Maybe they changed priorities? Does it happen that some task is postponed for later again and again? Of course, it happens to everyone. Sometimes it becomes a problem. The problem is that we try to keep a very large number of tasks in our heads. It doesn't have to be big coding tasks. It could be checking email, committing to reply to someone, pending code reviews, and so on. The problem is that the more such tasks we try to remember, the more energy we spend on it. The solution is a to-do list. Personally, I approach it in two stages:</p>
<ol>
<li>First, I write out all my plans. I mean, all my plans and desires. All that I am going to do in one way or another, both in the near future and later. After that, this list is edited. I remove everything that I consider unnecessary in the current situation. It's not just crossing off an item from the list, it's a decision not to move in that direction, not to spend any energy on achieving the goal. Complete rejection of the target.</li>
<li>And then I sort this list by urgency and distribute it into several categories. For example, household chores and work chores. Plus I mark some cases as urgent. The purpose of this list is to continue to do only what is on it. To remove cognitive load from trying to remember and correctly prioritize all tasks on the go.
It sounds crazy, that's for sure. For me, it also looked like strange crap at first. But you should try too. This method will free you up a significant amount of resources, allowing you to work effectively for much longer than is currently possible. And I immediately answer the question about the situation when you need to do something that is not on the list: put this business on the list. Once again: don't do anything that isn't on the list, and if something still needs to be done, put it on the list.</li>
</ol>
<h3>Frequent switching of attention</h3>
<p>People have this feature: we need time to switch to different types of tasks. In other words, if you were working and something distracted you, then it will take you up to half an hour to get back to working condition and work as efficiently as you worked before. And this is if you are in a good condition: you slept well, you are not tired, and so on. In a less productive state, this process will take even longer. Thus, a simple recommendation appears: for the duration of work, ensure the most comfortable environment for yourself, eliminate distractions: turn off notifications on your phone, ask your wife not to disturb you, send your children to kindergarten, and so on. Just imagine: if every 10 minutes notifications come to your messenger, it will unsettle you for 10 minutes. And this means that during the working day you will not start working as efficiently as you can. And, I assure you, it only seems to you that the notifications from the chat do not distract you: in fact, for a split second, your mind is distracted by the thought: “Do I need to read this message?”. And it knocks you off the rhythm.</p>
<h3>Excessive endorphin bursts</h3>
<p>The result of another extreme between &quot;I want&quot; and &quot;I have to&quot;. When we fulfill our desires, we get a charge of endorphins directly to the brain. And if we regularly follow our wishes, then over time we get dependent on them and inevitably come to the simplest ways to get a dose of endorphins. These are food and all kinds of feeds: tiktok, instagram, TV series, reading the contents of chats, etc. And, of course, games. Especially - games with short rounds and clickers. All these things are united by one thing: each of them is an easy and affordable way to get a dose of endorphins. And the brain loves them. If you have reached this stage, the path to productivity will be associated with withdrawal symptoms. You will always want more. At least one sandwich, at least one eye to watch the series, just one quest to complete in the game ... Believe my experience 😄 At some point, I realized that I always fill every free minute with my phone. If there is very little time or no desire to do something specific, I scroll through the chats. In other cases - games and TV series. Compared to this, all other pastimes have become boring, including work. And technically, the point is that the receptors in the brain have developed an addiction to endorphins. And the only way is to reduce the dose so that the sensitivity is restored, and the world sparkles with bright colors again. At the same time, a lot of time will be freed up for other things, and the overall tone will increase.</p>
<h3>Psychotherapist.</h3>
<p>Today, going to a psychotherapist is not such a bummer as it was thought 10 years ago. Don't neglect this opportunity. A good psychotherapist will definitely shorten your path from procrastination to productivity, will help you to quickly and effectively find and eliminate the causes of its occurrence.</p>
<h2>Postscript and Literature</h2>
<p>There are other reasons for procrastination, as well as other ways to deal with it. The main thing is not to perceive it as something abnormal and try to fight it. This is just a signal from the body. If nothing above suits you, or if you want to understand the issues of procrastination and efficiency better, I recommend this cool man, a professional procrastinatologist: <a href="https://procrastinatology.com/" target="_blank" rel="noopener noreferrer">procrastinatology.com</a>.<br>
I'm not sure if he has much material in English, but at least look at the given site.</p>
<p>As always, I'd be glad to discuss in <a href="https://t.me/viktorprogger_channel_ru" target="_blank" rel="noopener noreferrer">the Telegram chat</a>. Feel free to supplement the article with your very own experience and ask any questions.</p>
<p>Have a nice day and don't burn out 😊</p>
]]></content>
    <category term="Development process"/>
    <published>2022-07-01T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">How to participate in an OpenSource project?</title>
    <id>https://viktorprogger.name/posts/how-to-start-develop-opensource.html</id>
    <link href="https://viktorprogger.name/posts/how-to-start-develop-opensource.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>We all use OpenSource products. Some of them we like.<br>
And for those we like, we often want to help to become even better. One way to help is financially. But we ourselves are developers, and sometimes we imagine ourselves among the authors of a wonderful tool that we use with pleasure. At least that's how it used to be for me. And then the dream became a reality.
If this is also a dream for you, you're welcome to the further reading: there is my story and the answer, how you can join to develop your favorite OpenSource tool too.</p>
]]></summary>
    <content type="html"><![CDATA[<p>We all use OpenSource products. Some of them we like.<br>
And for those we like, we often want to help to become even better. One way to help is financially. But we ourselves are developers, and sometimes we imagine ourselves among the authors of a wonderful tool that we use with pleasure. At least that's how it used to be for me. And then the dream became a reality.
If this is also a dream for you, you're welcome to the further reading: there is my story and the answer, how you can join to develop your favorite OpenSource tool too.</p>
<!-- more --> 
<p><img src="/opensource.jpg" alt></p>
<p><a href="#how-to-start-writing-code-in-an-opensource-project-yourself"><strong>TL;DR: How To</strong></a></p>
<div class="hint-container note">
<p class="hint-container-title">Thank you</p>
<p>Thanks <a href="https://leonidchernenko.ru/" target="_blank" rel="noopener noreferrer">Leonid Chernenko</a> for help with this article proofreading.
It is my first public work made in English, and I'm very thankful for his help and support.</p>
</div>
<h3>My story</h3>
<p>Now I'm one of the Yii3 core development team. I like this framework very much and I like to help it to become better.
The story about how did I come into the core team is as simple as possible. Once I came to the framework telegram chat and saw Alexander Makarov, the framework maintainer, answering the questions in the chat. He was answering every question, not just &quot;interesting&quot; or &quot;useful&quot; ones. It seems that he wrote something on the topic of being written to him in a PM, if there are any questions for him personally, but I'm not sure. Frankly, I was dumbfounded. You can say that Alexander was my idol, and suddenly - here he is, in a chat room where you can chat with him just like with my friends. That moment I've understood I didn't talk to him before just because I've decided he won't want to talk to me. For the same reason I didn't participate in the life of the Yii framework.</p>
<p>Then I decided on an adventure. I was scared, but I have texted Alexander anyway. That time I still was thinking he is smthng more than me. But I convinced myself that, first of all, we are both people, which means that communication is not alien to us. At last, I've written smthng like that:</p>
<blockquote>
<p>Hi! My name is Viktor, I'm a PHP developer. I like the Yii framework very much, and I'd like to help in developing of the new, the third version of the framework. Tell me please, how can I start?</p>
</blockquote>
<p>And he thanked me for my enthusiasm and gave me links to some GitHub tickets, which I soon got to work on. Tickets were really at the level of &quot;where to start&quot;. I don't remember what exactly it was, but it was the little things. Gradually, I got involved in this process and began to do more and more complex things. For example, I wrote the first version of the <code>yiisoft/validator</code> library. Not completely, just proof of concept. At that time I changed my main job and for some months I could not do OpenSource development. But the Yii core team guys took my code, finalized it and turned it into a full-fledged library. Now it has completely different architectural approaches, but for me personally it means a lot: it was my first serious contribution to the popular and, IMHO, the best framework.</p>
<h3>My results of the OpenSource development</h3>
<p>Of course, I got a lot of experience. I continue to happily engage in OpenSource in my free time and continue to get more and more new experiences. To single out the most striking acquisitions, I will name these points:</p>
<h4>Deep understanding of SOLID principles and overall application and library architecture.</h4>
<p>In commercial development, you usually don't have the opportunity to sit down and analyze various architectural solutions, write several versions and choose one of them, or even completely rework the original design with a deep refactoring. There are other values in OpenSource: while in commercial development one of the main criteria for choosing anything is the price/quality ratio, in the OpenSource the emphasis is always on quality. And now I use the experience of creating SOLID architectures, gained in OpenSource, in commercial development, where it is more important not to follow the principles to the end, but to understand where and how they should not be followed, what will be the price of their observance and violation.</p>
<h4>Writing tests.</h4>
<p>I had little experience writing tests before beginning the Yii3 development. Basically it were only the things I've learned myself. In YiiSoft we apply an integrated approach:</p>
<ul>
<li><a href="https://psalm.dev" target="_blank" rel="noopener noreferrer">Psalm</a>, statical typification checker.</li>
<li><a href="https://phpunit.readthedocs.io/ru/latest/" target="_blank" rel="noopener noreferrer">PHPUnit</a>, a framework for unit tests.</li>
<li><a href="https://infection.github.io/" target="_blank" rel="noopener noreferrer">Infection</a>, a tool for checking unit tests quality (mutational testing).</li>
</ul>
<p>It could mean nothing, but we do as full code coverage as possible, set Psalm level 1 and improve Infection MSI up to 100% for every package which is going to be released (see <a href="https://github.com/yiisoft/assets" target="_blank" rel="noopener noreferrer">yiisoft/assets</a> for example). It's very valuable for me that I've come to YiiSoft on start of the new evolution iteration of the framework. These tools and the way of their usage were selected by the whole team. We were covering the first packages with tests together. By the way, I've covered with tests my first package so badly that lately I was terrified. It was very difficult to maintain and evolve package with such tests. But this is the case when you learn from mistakes. Although it is these tests that have not yet been refactored at the time of this writing  😆</p>
<h4>Experience of working in a self-managing collective</h4>
<p>Yes, we all are equal in the team. We usually make more sense for Aleksander Makarov's (<a href="https://github.com/samdark" target="_blank" rel="noopener noreferrer">@samdark</a>) opinion. But he doesn't shy away from the others too. If anyone have questions or objections, we will discuss it till our common solution.</p>
<p>We've got our cons too. For example, for a long time it was Alexander who performed all managerial work. He was our boss in fact. He made most of architectural decisions, merged PRs and made releases. He also was the one who set the team's priorities.</p>
<p>Once he dropped out of the framework development for a couple of months. We had to get out ourselves. I'm glad to say: we did it. Not at once, but we changed our processes to avoid Alexander in most cases. We've phoned and discussed the most pressing issues, agreed on compromises and priorities. @Samdark came back in some time too. But he is not a bus-factor for the team since then. He is still the most respected team member and the framework maintainer. But now he is doing more in terms of helping the team to improve processes and discuss decisions instead of direct managing. <em>This is a wonderful experience: people united by one idea, on their own initiative, cooperated and are doing a common thing.</em> And most of us are doing this thing for free. You can get a similar experience in a &quot;teal company&quot;, I think. But there are still a few such companies, so OpenSource is more simple way to get it.</p>
<h4>Summary</h4>
<p>The above points are the experience that turned out to be the most vivid for me. It will be smthng else for you. But the most important thing is that everyone can find in OpenSource a lot of experience, not available on commercial development. Not to mention how valuable the line that you have experience developing a popular OpenSource tool is on your CV.</p>
<h3>How to start writing code in an OpenSource project yourself</h3>
<p><img src="/tom_jerry_wait.jpg" alt></p>
<p>It doesn't matter which one of the OpenSource projects do you like the most. Its team will be glad to get your help. The good news is that it's pretty easy to start writing code for it. The necessary steps are:</p>
<ol>
<li>Find out what help do they need. Find an actual issue list or ask in chat.</li>
<li>Write the corresponding code and get review for it.</li>
<li>Fix the review comments, if any.</li>
</ol>
<p>That's all. After that hundreds/thousands/millions of people will use your code! All you need to continue is just to reiterate the above steps again.</p>
<p>The most difficult thing is to do the first step on this path. You should find out the way you can help. It is really pretty easy: the only thing you have to do is to ask a question in the tool community. I'm sure all the OpenSource tools have their own chats or forums for now. I.e. Yii3 has telegram chat rooms <a href="https://t.me/yii3en" target="_blank" rel="noopener noreferrer">@yii3en</a> and <a href="https://t.me/yii3ru" target="_blank" rel="noopener noreferrer">@yii3ru</a>. The algorithm is simple: go into the chat, express your desire to help and ask them to submit a ticket that should be dealt with. If it's your first time, you'd rather all for smthng more simple to get into the development process before taking more complex things.</p>
<h3>What prevents you from joining an OpenSource product</h3>
<p>The algorithm above is really outrageously simple, its implementation is available to everyone. But why do so few people do it?</p>
<h4>1. I have no time</h4>
<p>You have a full-time job and a family, I guess. And you're busy for 24 hours in a day. Maybe even for 25 hours. However, the secret is simple: you can do OpenSource in your spare working time. It may seem that &quot;spare&quot; and &quot;working&quot; are incompatible things, but it is not so. The fact is that we are really productive only 2-4 hours out of the whole working day. The rest of the time is occupied by other things: code reviews, routine, bureaucracy, etc. But this is ideally, and usually all this time is occupied by procrastination. Consider: do your tasks really take as much time to complete as they do? Don't they? Then I propose an experiment: do your work for 2 hours a day with maximum immersion in it, without being distracted by anything. Prepare a clear plan of action, switch all chats and mail to silent, and go ahead. The second part of the plan is to be sure to dedicate part of your time to what brings pleasure, what you sincerely enjoy. Personally, I sometimes spend this time developing a framework 😃 Well, a significant part of the time - for sluggish work tasks that do not require much concentration. And do not forget about lunch, where you will not be doing work in any form for an hour. It is also desirable to arrange a walk or some kind of physical activity and exercises during breaks.<br>
I bet that with this mode, your efficiency will increase, and part of the freed up time can be given to your favorite OpenSource project.</p>
<h4>2. I'm inexperienced</h4>
<p>So OpenSource is for you! This is an excellent place to gain experience. As a rule, there are enough tasks of very different complexity: not only to develop new architectural approaches and performance optimization, but also various simple but necessary little things: change the code style, add documentation, rename files and classes, etc.  And you will help a lot if you take these things upon yourself. At the same time, you are free to take more complex issues where you need to figure out for implementation, and even ask for help from the main developers of the product. Entering OpenSource development is possible at any stage of professional development if you already know well the programming language used in the product.</p>
<h4>3. I'm afraid I'll be laughed at</h4>
<p>It was very strange for me to hear this reason. I have never met developers of OpenSource tools so toxic that they somehow ridicule those who want to help them. For what? The only thing that comes to mind is that your friends, having heard that you decided to make your contribution to OpenSource, out of envy and self-doubt, they may try to somehow insult you, stop you, so that in no case it turns out that you are &quot;better than them&quot;.  Throw such acquaintances if you have them: you should not go under with them.  Our path is only forward!</p>
<h4>4. I'm scared</h4>
<p>Of course, you are! Like anyone else who is going to start something new in his life. It's ok. There is no personal development without fear. A brave person is not one who is not afraid, but the one who acts in spite of fear. Therefore, the best decision is to be afraid and do it 😊 Find new colleagues, and who knows, maybe - friends and a mission?</p>
]]></content>
    <category term="Development process"/>
    <published>2022-06-10T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">How to use PHP without installing it (Linux)</title>
    <id>https://viktorprogger.name/posts/how-to-use-php-without-installing.html</id>
    <link href="https://viktorprogger.name/posts/how-to-use-php-without-installing.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>The answer is definitely simple: use Docker 😃 Benefits I get from using PHP like this:</p>
<ul>
<li>No need to bother with setting up a local environment, switching PHP versions, installing additional libraries and resolving conflicts</li>
<li>One text file with command line aliases is enough for any version of PHP to work on any computer. And from here
<ul>
<li>Ease of transferring data between machines</li>
<li>You can safely demolish the system on your main computer, restoring work with PHP will be as simple as <code>git pull</code></li>
</ul>
</li>
</ul>
<div class="hint-container note">
<p class="hint-container-title">Note</p>
<p>This article is about parsing the use of docker in one specific scenario, so if you are already familiar with this technology, you can safely skip it. The rest of you are welcome to the further reading.</p>
</div>
<img src="/php-docker.png" style="width: 100%" alt="How to use PHP without installing it">
]]></summary>
    <content type="html"><![CDATA[<p>The answer is definitely simple: use Docker 😃 Benefits I get from using PHP like this:</p>
<ul>
<li>No need to bother with setting up a local environment, switching PHP versions, installing additional libraries and resolving conflicts</li>
<li>One text file with command line aliases is enough for any version of PHP to work on any computer. And from here
<ul>
<li>Ease of transferring data between machines</li>
<li>You can safely demolish the system on your main computer, restoring work with PHP will be as simple as <code>git pull</code></li>
</ul>
</li>
</ul>
<div class="hint-container note">
<p class="hint-container-title">Note</p>
<p>This article is about parsing the use of docker in one specific scenario, so if you are already familiar with this technology, you can safely skip it. The rest of you are welcome to the further reading.</p>
</div>
<img src="/php-docker.png" style="width: 100%" alt="How to use PHP without installing it">
<!-- more -->
<h3>What Aliases are</h3>
<p>An alias is an additional or overridden command in the terminal that, when called, actually calls another command. For example, if you always write <code>ls -la</code> to list files in a folder, it makes sense to use the alias <code>alias ls='ls -la'</code>. Thus, every time you type <code>ls</code> in the terminal, <code>ls -la</code> will actually be called.<br>
Creating an alias is a terminal command. Those, you can write <code>alias ls='ls -la'</code> in the terminal, and it will work. But only in the current terminal session. For this to always work, you need to run such a command in all sessions. This is done differently in different shells. I will talk about how this is done in Bash, which is the default in most linux distributions. If you are using ZShell or another one, we will assume that you are aware of how aliases are created in it.</p>
<p>So bash. Any bash user can create a <code>.bashrc</code> file in their home directory, which is always executed when the terminal starts. Ubuntu creates it by default, and by default it makes the following entry in it:</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> [ </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-f</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ~/.bash_aliases ]; </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">then</span></span>
<span class="line"><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">    .</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> ~/.bash_aliases</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">fi</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>That is, it includes the <code>.bash_aliases</code> file in the home directory, if it exists. I write all my aliases in <code>.bash_aliases</code> to keep them in a separate file. And now - let's start studying its contents.</p>
<h3>PHP</h3>
<p>I wrote this set of aliases to work with PHP:</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">alias</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> php</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'docker run --rm -it -u `id -u`:`id -g` --volume `pwd`:/app -w /app ghcr.io/mileschou/xdebug:8.1 php  $@'</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">alias</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> php81</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'docker run --rm -it -u `id -u`:`id -g` --volume `pwd`:/app -w /app ghcr.io/mileschou/xdebug:8.1 php  $@'</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">alias</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> php8</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'docker run --rm -it -u `id -u`:`id -g` --volume `pwd`:/app -w /app ghcr.io/mileschou/xdebug:8.0 php  $@'</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">alias</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> php74</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'docker run --rm -it -u `id -u`:`id -g` --volume `pwd`:/app -w /app ghcr.io/mileschou/xdebug:7.4 php  $@'</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>I end up typing commands like <code>php -a</code> for interactive work or <code>php vendor/bin/phpunit</code> for running tests in the terminal, and everything works as if php is local on my machine. And when I need a different version of php for some project, I write, for example, <code>php74 vendor/bin/psalm</code>.</p>
<p>So, let's go to parse the above commands. For each version, I have written the same thing, only the version itself changes.</p>
<ul>
<li><code>docker run</code> starts a new container based on the specified image (it is at the end of the command). <a href="https://docs.docker.com/engine/reference/commandline/run/" target="_blank" rel="noopener noreferrer">Docs for the <code>docker run</code></a>. All the above options of this command are described on the same documentation page.</li>
<li><code>--rm</code> - this parameter says that the container should be removed after it finishes its work. If you do not specify this, for every <code>docker run</code> a new container will be created, after stopping it will not go anywhere, and soon you will run out of hard disk space.</li>
<li><code>-it</code> - this parameter is needed to work in an interactive command line session. If you do not specify it, then those php commands that are waiting for some kind of reaction from the user (for example, <code>php -a</code> or scripts that ask for data input) will not work.</li>
<li><code>-u</code> starts the container as a specific user. By default, the container runs as root, which can lead to permission issues with files that are mounted from the local machine and modified/created in the container. <code>`id -u`</code> and <code>`id -g`</code> returns the user and group id respectively, and in most cases it will be <code>1000</code>. These commands are enclosed in backticks so that they are executed before the alias is called. As a result, the command in the alias will be called with the following value of the user parameter: <code>-u 1000:1000</code> if your user and group id are both <code>1000</code>.</li>
<li><code>--volume `pwd`:/app</code> - this option mounts the current path in the file system (<code>`pwd`</code>) to the container at <code>/app</code> path. In the other words, your files from the current folder will be available in the container at the path <code>/app</code>.</li>
<li><code>-w /app</code> - set the path <code>/app</code> as the working folder in the container.</li>
<li><code>ghcr.io/mileschou/xdebug:8.1</code> - the name of the container image to run and its tag (version). I use this image instead of the official php because there is no XDebug in the official one, and sometimes you need debugging or calculating code coverage with tests. The tag is always specified after the colon, in this example it is <code>8.1</code></li>
<li><code>php $@</code> is a command that runs inside the container. However, <code>$@</code> is a bash variable pointing to the line after the alias. I'll show you with an example. If I type <code>php vendor/bin/phpunit --foo --bar</code> in the terminal, the <code>$@</code> variable will be <code>vendor/bin/phpunit --foo --bar</code>.</li>
</ul>
<h4>PhpStorm</h4>
<p>PhpStorm works great with containers. In order to be able to run tests, static analysis or other scripts from PhpStorm with a button, you need to add a container as an interpreter. <a href="https://www.jetbrains.com/help/phpstorm/configuring-remote-interpreters.html#487450a9" target="_blank" rel="noopener noreferrer">Docs</a>
<img src="/phpstorm-settings-php-interpreter-docker.png" alt="phpstorm-settings-php-interpreter-docker.png"></p>
<p>However, the adventure doesn't end there. There is another common use case for php in the terminal, Composer.</p>
<h3>Composer</h3>
<p>With the Composer, everything is as similar as possible. However, the command here is much longer, because environment variables and volumes are added. They are needed for the two purposes:</p>
<ul>
<li>to store settings and composer cache between container launches</li>
<li>and to transfer data for ssh authorization from the host machine to the container.</li>
</ul>
<p>I have two aliases for it to be able to work with a project where the first version is hardwired. Here I use official container images.</p>
<div class="language-bash line-numbers-mode" data-highlighter="shiki" data-ext="bash" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-bash"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">alias</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> composer</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'docker run --rm -it --tty -u 1000:1000 -e COMPOSER_HOME=/composer-data/.composer -e COMPOSER_CACHE_DIR=/composer-data/.composer/cache -e SSH_AUTH_SOCK=/ssh-auth.sock --volume `pwd`:/app --volume /home/`whoami`/.composer:/composer-data/.composer --volume /run/user/1000/keyring/ssh:/ssh-auth.sock --volume /etc/passwd:/etc/passwd:ro --volume /etc/group:/etc/group:ro composer:2 $@'</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">alias</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> composer1</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'docker run --rm -it --tty -u 1000:1000 -e COMPOSER_HOME=/composer-data/.composer -e COMPOSER_CACHE_DIR=/composer-data/.composer/cache -e SSH_AUTH_SOCK=/ssh-auth.sock --volume `pwd`:/app --volume /home/`whoami`/.composer:/composer-data/.composer --volume /run/user/1000/keyring/ssh:/ssh-auth.sock --volume /etc/passwd:/etc/passwd:ro --volume /etc/group:/etc/group:ro composer:1 $@'</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>In addition to the parameters that I have already analyzed above, several more are used here. I will not repeat myself and will focus only on the new ones.</p>
<ul>
<li><code>-e</code> - this option sets the environment variable inside the container. I have several of them:
<ul>
<li><code>COMPOSER_HOME=/composer-data/.composer</code> - where the Composer will look for and store its data (for example, access tokens on GitHub)</li>
<li><code>COMPOSER_CACHE_DIR=/composer-data/.composer/cache</code> - where the Composer will look for and store its cache. This is necessary for optimization. If you do not store the cache on the host, it will re-download all packages every time. If there is a package of the required version in the cache, the Composer will simply copy the files from the cache to the project.</li>
<li><code>SSH_AUTH_SOCK=/ssh-auth.sock</code> - path to authorization socket for ssh agent</li>
</ul>
</li>
<li>Plus I'm mounting a few more volumes from the host. These are pairs to the above environment variables.
<ul>
<li><code>--volume /home/`whoami`/.composer:/composer-data/.composer</code> - save composer data on the host between container launches</li>
<li><code>--volume /run/user/`id -u`/keyring/ssh:/ssh-auth.sock</code> - forward an authorization socket for the ssh agent into the container</li>
<li><code>--volume /etc/passwd:/etc/passwd:ro</code> and <code>--volume /etc/group:/etc/group:ro</code> dump user accounts from the host in read-only mode (the <code>:ro</code> at the end is responsible for this) into the container. It is worth noting right away that this is not safe, and it is worth doing this only if you understand what you are doing and why. Personally, I believe that the official images of the composer are trustworthy, but this is just my personal opinion. And without these volumes, authorization on the GitHub via SSH with keys from the host machine will not work.</li>
</ul>
</li>
</ul>
<p>It is worth remembering that the container contains the minimum assembly required for the composer to work. This means that sometimes you will have to append the <code>--ignore-platform-reqs</code> flag to the command you are calling: <code>composer i --ignore-platform-reqs</code>.</p>
<p>If you have any questions or suggestions for improving aliases - <a href="https://t.me/viktorprogger_channel_ru" target="_blank" rel="noopener noreferrer">welcome to the blog chat</a>, let's chat.</p>
]]></content>
    <category term="Development Techniques"/>
    <published>2022-08-19T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">Public or private by default, what to choose?</title>
    <id>https://viktorprogger.name/posts/public-or-private-by-default-what-to-choose.html</id>
    <link href="https://viktorprogger.name/posts/public-or-private-by-default-what-to-choose.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>When I worked on another project, I met an interesting opinion. They say, all the code in the project should be as open as possible by default. I.e. we should make all new methods and fields <code>public</code>, and we should not put <code>final</code> anywhere, except some special cases when it's really necessary. It was motivated with the difficulties of further codebase support:</p>
<blockquote>
<p>If I want to inherit the class, and there is a private method, I have to study the code to understand why it was made <code>private</code> and whether I can make it <code>public</code>.</p>
</blockquote>
<p>This opinion was very unusual for me, and I decided to figure it out if there is really more problems when fields and methods are private by default.<br>
Let's write an extra public method and see how easy is it to support it.</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> FooInterface</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> foo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Foo</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> FooInterface</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> foo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // do stuff</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // do stuff</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // do stuff</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div>]]></summary>
    <content type="html"><![CDATA[<p>When I worked on another project, I met an interesting opinion. They say, all the code in the project should be as open as possible by default. I.e. we should make all new methods and fields <code>public</code>, and we should not put <code>final</code> anywhere, except some special cases when it's really necessary. It was motivated with the difficulties of further codebase support:</p>
<blockquote>
<p>If I want to inherit the class, and there is a private method, I have to study the code to understand why it was made <code>private</code> and whether I can make it <code>public</code>.</p>
</blockquote>
<p>This opinion was very unusual for me, and I decided to figure it out if there is really more problems when fields and methods are private by default.<br>
Let's write an extra public method and see how easy is it to support it.</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> FooInterface</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> foo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Foo</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> FooInterface</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> foo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // do stuff</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // do stuff</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // do stuff</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><!-- more -->
<p>Let us agree that at the time of writing the code, the <code>bar</code> method is no longer used anywhere, and technically we do not care whether to make it public or private.</p>
<h2>Private</h2>
<p>If we make it private, we will encounter the voiced difficulties: before making the logic of this method public, the programmer will have to familiarize himself with the method code and find out if there are places specific to this particular class. For example, working with a state.</p>
<h2>Public</h2>
<p>If you make the method public by default, we will avoid the likelihood that we will need to perform the work described above in the future. Just kidding, of course, there is no escape 😄</p>
<div class="hint-container note">
<p class="hint-container-title">Note</p>
<p>Nobody guarantees that a programmer will write the method strictly in such a way that it can be reused from any other place in the application. Especially since initially the method is not planned to be reused.</p>
</div>
<p>Let's think about what other problems we can face in a few years after writing a such code. Let's imagine that we did not change this method of the year 3. And now we need to remove it or change the signature. The first question that arises: is this method used in other places of the project? Anyone of company developers could call a public method from any other place, and they all need to be found. You can say that modern IDEs solve this issue instantly, but what if the black magic of frameworks was used?</p>
<div class="hint-container note">
<p class="hint-container-title">Note</p>
<p>All these <code>__get()</code> and<code> __call()</code>, calling the method through an arbitrary string identifier, Yii2 behaviors, Laravel facades, etc... The presence of such approaches in the project makes the task a non-trivial one.</p>
</div>
<p>Complicating it a little more: we found several places where the <code>bar</code> method was used, but it is not entirely clear why, and the authors of that code no longer work in your company. What to do in this case?</p>
<p>Let's add another potential problem to the piggy bank. Some developer decided to slightly change the functionality of <code>bar</code>, but did not deeply seek for usages of this method. And they were. But they were hidden.</p>
<h2>Conclusions</h2>
<p>The public way does not solve the problem voiced for the private one, but adds a heap of new difficulties. Therefore, I definitely recommend that all fields, constants and methods of classes be made private if they are not used outside the class in which they are declared, and are not explicitly intended for this.</p>
<h2>An example of private-to-public refactoring</h2>
<p>So, in our project there is a version of the <code>Foo</code> class with the private<code> bar</code> method. At some point, it became obvious that the functionality of this method was needed in a couple of another places. In most cases you can take <code>bar</code> to a separate class, which can be used anywhere in the project using your favorite DI Container. The full code will look like this:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // do stuff</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> FooInterface</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> foo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Foo</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> implements</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> FooInterface</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">  __construct</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Bar</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> foo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">() {</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // do stuff</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">    $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">bar</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">();</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // do stuff</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>In this case a public contract for the <code>bar</code> functionality will be concluded exactly at the moment when it became needed. Accordingly, the attitude towards it will be appropriate: as a consciously realized contract, and not &quot;another public method, which is unlikely to be used anywhere.&quot; Because in such a project, nothing public is just done.</p>
]]></content>
    <published>2023-04-30T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">Stateless services in PHP</title>
    <id>https://viktorprogger.name/posts/stateless-services-in-php.html</id>
    <link href="https://viktorprogger.name/posts/stateless-services-in-php.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>Imagine you've developed an order processing service that stores information about the current user and collects items in a shopping cart. Everything works perfectly in a simple web application where each request is handled by a separate PHP process. But one day, the project requirements change.</p>
<img src="/stateless-services-php.png" style="width: 100%" alt="Stateless services in PHP">
]]></summary>
    <content type="html"><![CDATA[<p>Imagine you've developed an order processing service that stores information about the current user and collects items in a shopping cart. Everything works perfectly in a simple web application where each request is handled by a separate PHP process. But one day, the project requirements change.</p>
<img src="/stateless-services-php.png" style="width: 100%" alt="Stateless services in PHP">
<!-- more -->
<h3>The Trap of Long-Running Processes</h3>
<p>Your application grows, and you decide to optimize its performance by switching to a modern web server like <strong>RoadRunner</strong>, <strong>Swoole</strong>, or <strong>FrankenPHP</strong>. Now, PHP processes don't die after each request but are <em>reused</em>. And here's where the interesting part begins: data from one user randomly &quot;leaks&quot; into the requests of another user because the service's state persists between requests.</p>
<p>Or imagine you decide to process orders asynchronously via a message queue. The worker processing the queue lives for a long time and handles thousands of messages in one lifecycle iteration. If your service maintains state, each unprocessed message can affect the processing of subsequent ones, creating tangled and hard-to-debug errors.</p>
<div class="hint-container info">
<p class="hint-container-title">Info</p>
<p>In such long-running processes, every line of code that saves state in class properties becomes a potential problem:</p>
<ul>
<li>State accumulates unnoticed, consuming more and more memory</li>
<li>Parallel operations start conflicting with each other</li>
<li>Debugging becomes a real quest because the service's behavior depends on its previous calls</li>
</ul>
</div>
<h3>Evolution of Usage</h3>
<p>Even if your service is currently used in a simple context, without these trendy long-running states, its application might evolve:</p>
<ul>
<li>Code that was called once starts being called in a loop</li>
<li>A simple HTTP request handler is moved to a console command</li>
<li>Synchronous operations become asynchronous</li>
<li>The need for scaling and parallel execution arises</li>
</ul>
<p>In each of these cases, the presence of internal state becomes a source of problems and requires code refactoring.</p>
<h2>Solution: Stateless Services</h2>
<p>A <strong>Stateless service</strong> is a class that does not store internal state between calls to its methods. Each method works only with the data passed to it as parameters.<br>
This approach allows abstracting away from the context in which the service is used.
A stateless service can be called once, in a loop, or in a long-running application.
It doesn't matter to it.</p>
<p>What are the alternatives? Services with state, or <em>stateful</em>. Let's consider an example I often see in various projects.</p>
<h3>Example of a Stateful Service</h3>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> OrderProcessor</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> PaymentGateway</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> float</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ?</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $currentUser</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> __construct</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">PaymentGateway</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">float</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">void</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">currentUser</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> processOrder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">array</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $items</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">void</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        $total</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">calculateTotal</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$items</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">charge</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">$this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">currentUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$total</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> calculateTotal</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">array</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $items</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">float</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        $subtotal</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> array_sum</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">array_map</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            fn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$item</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#61AFEF"> => </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$item</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'price'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> *</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $item</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'quantity'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">],</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            $items</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        ));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $subtotal</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> *</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>In this example, it's essential to set the correct user as the service's state. And the result of the <code>processOrder()</code> method depends entirely on it. If, for some reason, the <code>setUser()</code> method was not called, it will attempt to charge money from the wrong person. What else could go wrong? Anything. The main reason is the human factor: you can be sure that sooner or later, one of the project's programmers will fail to ensure the correct user is set in the service, leading to a serious error. Here's what could happen technically:</p>
<ul>
<li>The method was forgotten to be called</li>
<li>After setting the correct user, another user was set (e.g., during the processing of an event chain between the <code>setUser()</code> and <code>processOrder()</code> calls)</li>
<li>After setting the correct user, the service instance was replaced</li>
</ul>
<div class="hint-container info">
<p class="hint-container-title">Info</p>
<p>When a project is maintained for years, the probability of such an error increases to almost 100%. Meanwhile, fixing it would be very easy.</p>
</div>
<h3>Example of a Stateless Implementation</h3>
<p>To get rid of the state in the example above, simply remove the user field and the corresponding method, and add the user itself as a parameter to the method that actually depends on it:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> OrderProcessor</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> PaymentGateway</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> float</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> __construct</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">PaymentGateway</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">float</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> processOrder</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">array</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $items</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">void</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        $total</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">calculateTotal</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$items</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">charge</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$total</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> calculateTotal</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">array</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $items</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">float</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">        $subtotal</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> array_sum</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">array_map</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">            fn</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$item</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span><span style="--shiki-light:#383A42;--shiki-dark:#61AFEF"> => </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$item</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'price'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> *</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $item</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'quantity'</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">],</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">            $items</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        ));</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        return</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $subtotal</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> *</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> +</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h3>Advantages of This Approach</h3>
<ol>
<li>
<p>Predictability:</p>
<ul>
<li>Method behavior depends only on input parameters</li>
<li>The same input always yields the same result (in this example, we always call <code>paymentGateway-&gt;charge()</code> with the same arguments as a result)</li>
<li>No implicit dependencies between calls</li>
</ul>
</li>
<li>
<p>Ease of Testing:</p>
<ul>
<li>No need to worry about initial state</li>
<li>Tests become simpler and clearer</li>
<li>Methods can be tested in isolation</li>
</ul>
</li>
<li>
<p>Thread Safety:</p>
<ul>
<li>Can be safely used in a multi-threaded environment</li>
<li>No problems with scaling</li>
<li>Absence of race conditions</li>
</ul>
</li>
</ol>
<h2>Understanding Different Data Types in Services</h2>
<p>Not everything stored in class properties is harmful state. Let's break down three fundamentally different types of data:</p>
<h3>1. Configuration</h3>
<p>These are immutable values that define the service's behavior:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> OrderProcessor</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> readonly</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> float</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> readonly</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> float</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $minimumOrderAmount</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    </span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> __construct</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">float</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">float</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $minimumOrderAmount</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $taxRate</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">minimumOrderAmount</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $minimumOrderAmount</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Configuration settings are acceptable in class properties because they:</p>
<ul>
<li>Do not change during the object's lifetime</li>
<li>Define the service's base behavior</li>
<li>Are part of the application's configuration</li>
<li>Do not depend on the execution context</li>
</ul>
<h3>2. Dependencies</h3>
<p>These are other services or resources needed for operation:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> OrderProcessor</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> __construct</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> readonly</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> OrderRepository</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $orderRepository</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> readonly</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> PaymentGateway</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $paymentGateway</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        private</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> readonly</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> LoggerInterface</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $logger</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    ) {}</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Dependencies can be stored in properties, as they:</p>
<ul>
<li>Are tools for performing operations</li>
<li>Are usually stateless themselves</li>
<li>Are injected via the constructor</li>
<li>Do not change during operation</li>
</ul>
<h3>3. Ephemeral State</h3>
<p>This is exactly what should be avoided - data that changes during operation:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">// Anti-pattern!</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> BadOrderProcessor</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    private</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> ?</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $currentUser</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> null</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    </span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // Avoid methods like this!</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> setUser</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">User</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">): </span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B">void</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">        $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">currentUser</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>Design Recommendations</h2>
<ol>
<li>
<p>Configuration:</p>
<ul>
<li>Pass via constructor</li>
<li>Make immutable (<code>readonly</code>)</li>
<li>Use value objects for complex configurations</li>
</ul>
</li>
<li>
<p>Dependencies:</p>
<ul>
<li>Inject via constructor</li>
<li>Use interfaces</li>
<li>Do not change after object creation</li>
</ul>
</li>
<li>
<p>Ephemeral State:</p>
<ul>
<li>Pass via method parameters</li>
<li>Avoid <code>set*</code> methods</li>
<li>Use DTOs to group parameters</li>
<li>Store in DB or cache if necessary</li>
</ul>
</li>
</ol>
<h2>Conclusion</h2>
<p>Designing stateless services is not just a trendy architectural decision. It's a necessity in modern PHP development, where applications must be ready for scaling, parallel execution, and operation in long-running processes.</p>
<p>A proper understanding of the differences between configuration, dependencies, and ephemeral state allows for the creation of clean and maintainable services that are easy to test and safe to use in any context.</p>
<p>Remember: a service that handles a single request today might become part of a complex asynchronous process tomorrow. By designing it stateless from the beginning, you protect yourself from numerous potential problems in the future.</p>
]]></content>
    <category term="Development Techniques"/>
    <published>2025-04-21T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">The two class types for your project</title>
    <id>https://viktorprogger.name/posts/two-class-types-for-your-project.html</id>
    <link href="https://viktorprogger.name/posts/two-class-types-for-your-project.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>The question of the area of responsibility of a particular class in a program architecture is no less important than architecture strategic planning.  Today I will tell you how to make this job easier for yourself.  We will solve a fairly large number of issues, leaving only two types of classes out of an infinite variety: DTO and Service. These types cover 90-99% of everything needed in any project. So, let's look at what they are, and why it's so good to leave only them.</p>
<img src="/chaos.jpg" style="width: 100%" alt="The two class types for your project">
]]></summary>
    <content type="html"><![CDATA[<p>The question of the area of responsibility of a particular class in a program architecture is no less important than architecture strategic planning.  Today I will tell you how to make this job easier for yourself.  We will solve a fairly large number of issues, leaving only two types of classes out of an infinite variety: DTO and Service. These types cover 90-99% of everything needed in any project. So, let's look at what they are, and why it's so good to leave only them.</p>
<img src="/chaos.jpg" style="width: 100%" alt="The two class types for your project">
<!-- more -->
<div class="hint-container info">
<p class="hint-container-title">Info</p>
<p>Here and below, we will use an online store as an example.</p>
</div>
<p>There are two main approaches to entities: the so-called &quot;rich&quot; and &quot;anemic&quot; models.  The rich model implies that the entity itself knows what can be done with it and how. Let's say we have such an entity as &quot;order&quot;. In a rich domain model, its interface would look like this:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">interface</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> OrderInterface</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">():</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> OrderId</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getProducts</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">():</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> array</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getCustomer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">():</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Customer</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> getStatus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">():</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> OrderStatus</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> create</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">():</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> void</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> update</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">():</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> void</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> delete</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">():</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> void</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> moveTo</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">OrderStatus</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $status</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">):</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> void</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> removeProduct</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B">Product</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $product</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">):</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> void</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">    // etc.</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>It has getters and some methods for its changing: saving to a DB, status modifying (FSM?), etc.</p>
<p>The logic says there will be problems with the Single Responsibility and Interface Segregation principles (from the SOLID), and code coupling of such a project will be very high. Just imagine how many actions in other subsystems need to be done to create an Order: payments, logistics, warehouse, etc. Besides, I've never seen a successful appliance of a rich model. Maybe, I just don't know how to deal with it. If you have an example of a project in which it is justifiably applied, I'll be glad to see its code.</p>
<p>So, let's dwell on the anemic model in more detail. It is called so because the entity has no additional responsibilities and no knowledge of anything other than its own state. That is, if we take the example above, the Order will have only getters. We can even drop them since php 8.1:</p>
<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">class</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> Order</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">{</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> __construct</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> readonly</span><span style="--shiki-light:#C18401;--shiki-dark:#E5C07B"> OrderId</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">        /** </span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#C678DD;--shiki-dark-font-style:italic">@var</span><span style="--shiki-light:#C18401;--shiki-light-font-style:italic;--shiki-dark:#E5C07B;--shiki-dark-font-style:italic"> Product</span><span style="--shiki-light:#A626A4;--shiki-light-font-style:italic;--shiki-dark:#E5C07B;--shiki-dark-font-style:italic">[]</span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"> */</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">        public</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> readonly</span><span style="--shiki-light:#A626A4;--shiki-dark:#E5C07B"> array</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75"> $products</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        public readonly Customer $customer;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        public readonly OrderStatus $status;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    ) {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    }</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>An entity in an anemic domain model is called DTO, Data Transfer Object.</p>
<div class="hint-container note">
<p class="hint-container-title">Note</p>
<p>There is the third approach, and it is really used in 99 cases of 100. It's a combination of the two above. There is no clear separation in class responsibility: entities contain business-logic, services has state. This is the most unpleasant option, because in support it becomes more and more expensive every day. It becomes more and more difficult to develop the system, programmers no longer have the pleasure of working with it, and nothing can be done about this situation without global refactoring.</p>
</div>
<h2>Data Transfer Object</h2>
<p>DTO is just a representation of some data, which travels from one place in a system to another. Ideally, a DTO is created at the entrance to the system and served at the exit from it. For example, a web controller: a user sends data, it comes to a controller in the form of a <code>$_POST</code> array, where we immediately form the corresponding DTO of it. To the internal services of the system we give a fulfilled DTO, not a raw data. Inside the system the data goes exclusively in the form of DTO.</p>
<p>There are two key features of DTO:</p>
<h3>Nothing except data representation</h3>
<p>DTOs, by definition, lack any logic whatsoever. It is purely a representation of the data.
This approach allows DTO usage for end-to-end data transfer between different subsystems, including when this transfer is carried out over the network via API, queues, etc. Because it's just data, it's easy to be serialized and deserialized. The absence of business logic in data guarantees the absence of side effects from the transfer of an object to the next service or subsystem. It's very easy to support and change such classes.</p>
<h3>Immutability</h3>
<p>DTOs are inherently immutable, and this is their main advantage. What for? Just imagine that a DTO can change.
Let's say we have code like this: <code>$service-&gt;foo($dto);</code> and we know that the <code>foo</code> method does not need to change the state of the DTO passed to it at all. But time passes, the code changes, and somewhere down the chain of calls, not in <code>foo</code> itself, something like this appeared: <code>$dto-&gt;bar = 'baz';</code>. How will the code that comes after the call to <code>foo</code> react to this? When will you notice the behavior has changed? How many hours will it take to debug?
Either we can immediately make the object immutable by any of these ways:</p>
<ul>
<li>mark all fields <code>readonly</code></li>
<li>make fields <code>private</code> and provide access via getters</li>
<li>mark class <code>@psalm-immutable</code> if Psalm is used in your CI</li>
</ul>
<p><em>But what is the way to make changes to the data?</em>
No way. This is the point. If the business logic involves data changing, you must create a new object with this new data. Sometimes a DTO may imply such operations itself. To do so, <a href="https://github.com/yiisoft/docs/blob/master/010-code-style.md#immutable-methods" target="_blank" rel="noopener noreferrer">there are <code>with</code>-methods</a> in the conventions of the YiiSoft team, that return a new object of the same class with changed data. Personally I'd prefer calling a variable <code>$instance</code> rather than <code>$new</code>, but that's a minor point.</p>
<h2>Service</h2>
<p>The second type of classes needed in any project. In contrast to DTOs, services are stateless and implement some kind of logic. The example of services are repositories, controllers, factories, and other kinds of classes that <em>do something</em>. Often, services do some work on the data, that is, on the DTOs passed to them.<br>
What mistakes should be avoided when writing a service, and what benefits will we get?</p>
<h3>Service state</h3>
<p>This is the only truly blunder in the design of any service. If a service store some state, there are high chances of getting unexpected side effects on the second call of the service. But if a service doesn't store any state, it can easily be reused elsewhere. Or in the same place, within the same php-process, but with other data. And this not only facilitates further development, but also makes it possible to use tools like RoadRunner and Swoole.<br>
So, the wrong way: <code>$service-&gt;setData($data)-&gt;foo()</code>, the right way: <code>$service-&gt;foo($data)</code>. If you still find it necessary to leave state in your service for some individual methods to work, it is likely that you should split this service into two.</p>
<p>It is worth making a remark about what the state of the class is. Its fields can contain various payloads: dependencies, settings, cache, and state.</p>
<ul>
<li>Dependencies are other services that allow the current one to do its job. We just follow the Dependency Inversion principle and deliver the dependencies to class through the constructor.</li>
<li>Settings are values that allow the service to work. Database connection data for the repository, web address for the API client, cache prefix, etc.</li>
<li>The runtime cache stores already calculated values in case these values do not change over time, and their recalculation is resource-intensive. Its usage looks like this:<div class="language-php line-numbers-mode" data-highlighter="shiki" data-ext="php" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-php"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">!</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">isset</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">$this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">cache</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">]) {</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">  $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">cache</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">] </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">return</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B"> $this</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">-></span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">cache</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">[</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">$id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">];</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div></li>
<li>The state is not set globally for the entire project/module, but changes depending on the context. This is the only kind of data in the service that can change during program execution. It is on it or with its help that the service performs some work.</li>
</ul>
<p>So good services can have dependencies, settings and runtime cache, while DTOs only have state.</p>
<p>Of course, there are other architectural patterns that don't fit into these two categories. But they are needed much less frequently. For example, I needed to implement the Builder only once in 10 years of work.</p>
]]></content>
    <category term="Development Techniques"/>
    <published>2022-06-17T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">An example of setting up xDebug in docker</title>
    <id>https://viktorprogger.name/posts/xdebug-docker-config-example.html</id>
    <link href="https://viktorprogger.name/posts/xdebug-docker-config-example.html"/>
    <updated>2026-08-29T21:02:16.000Z</updated>
    <summary type="html"><![CDATA[<p>Once upon a time, I put together a working environment with PHP, xDebug, Docker and PhpStorm. Since then, I have been dragging it from project to project and felt happy. For those who find it difficult to set up a local environment with Docker and xDebug, I am posting this config with explanations. Here below we will create a Docker image with xDebug installed, configure PhpStorm and explain a working configuration for Docker Compose.</p>
<img src="/xdebug-settings-example.png" style="width: 100%" alt="xDebug setup example in docker">
]]></summary>
    <content type="html"><![CDATA[<p>Once upon a time, I put together a working environment with PHP, xDebug, Docker and PhpStorm. Since then, I have been dragging it from project to project and felt happy. For those who find it difficult to set up a local environment with Docker and xDebug, I am posting this config with explanations. Here below we will create a Docker image with xDebug installed, configure PhpStorm and explain a working configuration for Docker Compose.</p>
<img src="/xdebug-settings-example.png" style="width: 100%" alt="xDebug setup example in docker">
<!-- more -->
<h2>Dockerfile</h2>
<p>Let's start with the <code>Dockerfile</code>. Installing xDebug in supported php versions has become quite trivial. All you have to do is install PECL itself, call <code>pecl install</code> and then enable xDebug in php settings. For an Alpine-based image (e.g. <code>php:8.2-fpm-alpine</code>), this command looks like this:</p>
<div class="language-dockerfile line-numbers-mode" data-highlighter="shiki" data-ext="dockerfile" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-dockerfile"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">RUN</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> apk add --no-cache linux-headers \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">	&#x26;&#x26; apk add --update --no-cache --virtual .build-dependencies $PHPIZE_DEPS\</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">	&#x26;&#x26; pecl install xdebug \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">	&#x26;&#x26; docker-php-ext-enable xdebug \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">	&#x26;&#x26; pecl clear-cache \</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">	&#x26;&#x26; apk del .build-dependencies</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Done, the image with xDebug has been built!</p>
<h2>PhpStorm</h2>
<p>Open the settings window, go to PHP -&gt; Servers and add a new server, be sure to specify the correct mappings.</p>
<div class="hint-container note">
<p class="hint-container-title">Note</p>
<p>It is a mapping between local files and files on the server. In our case, the server is the container inside which the application is located.</p>
</div>
<p><img src="/server-settings.png" alt></p>
<p>The most important parts are highlighted on the screenshot.</p>
<ul>
<li><strong>Absolute path on the server</strong>: we should specify which path the project files in the container are available in. In the <code>docker-compose.yml</code> example above, I mounted them as a volume into the container along the path <code>/var/www</code></li>
<li><strong>Name</strong>.  Yes, the name of the server in this case <em>plays a key role</em>, although this field is usually needed just for entering a human-readable name. In order to work correctly with <code>xDebug</code>, it must completely match to what we wrote in the <code>PHP_IDE_CONFIG</code> environment variable.  The config above says <code>serverName=docker</code>, so I also named the server <code>docker</code> in the PhpStorm settings. This is necessary so that PhpStorm can match local files with files on the server (in a container).</li>
</ul>
<div class="hint-container note">
<p class="hint-container-title">Note</p>
<p>There is another option for matching them: instead of <code>PHP_IDE_CONFIG</code>, you can use matching by <code>Host</code> and <code>Port</code> fields, but then debugging scripts run from the console will not work.</p>
</div>
<h2>docker-compose.yml</h2>
<p>My minimal <code>docker-compose</code> config usually looks like this:</p>
<div class="language-yml line-numbers-mode" data-highlighter="shiki" data-ext="yml" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-yml"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">version</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"3.8"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">services</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">  php</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    build</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      dockerfile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">.docker/php/Dockerfile</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      context</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    user</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"${USER_ID:-1000}:${GROUP_ID:-1000}"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    restart</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">unless-stopped</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    volumes</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">./:/var/www</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    working_dir</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">/var/www</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    environment</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      PHP_IDE_CONFIG</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${PHP_IDE_CONFIG:-serverName=docker}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      XDEBUG_MODE</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">${XDEBUG_MODE:-off}</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      XDEBUG_CONFIG</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"client_host=host.docker.internal"</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">      XDEBUG_TRIGGER</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">'yes'</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    extra_hosts</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">host.docker.internal:${HOST_IP:-172.17.0.1}</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">    dns</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">1.1.1.1</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> </span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      - </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">8.8.8.8</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Setting up <code>xDebug</code> here is done via environment variables, thanks to Derik Rethans for such a convenient way. All possible xDebug settings and corresponding environment variables can be viewed on the <a href="https://xdebug.org/docs/all_settings" target="_blank" rel="noopener noreferrer">dedicated page</a>.</p>
<div class="hint-container note">
<p class="hint-container-title">Note</p>
<p>The <code>${VARIABLE:-someValue}</code> notation has been brought into the <code>docker-compose</code> configs from <code>bash</code>. It means the following: &quot;return the value of the <code>VARIABLE</code> environment variable, but if it is not set or empty, return <code>someValue</code>&quot;.  In other words, <code>someValue</code> is the default value.  In order for it to work, you need to specify the characters <code>:-</code> between the name of the environment variable and this value.</p>
</div>
<p>Changing the value of environment variables during local development is very convenient via the <code>.env</code> file. It may contain something like this:</p>
<div class="language-dotenv line-numbers-mode" data-highlighter="shiki" data-ext="dotenv" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-dotenv"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># Change the name of the PHP server in the PhpStorm settings from docker to project-server</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">PHP_IDE_CONFIG</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">serverName=project-server</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic"># Enable xDebug, because it is disabled by default</span></span>
<span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">XDEBUG_MODE</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">develop,debug,coverage</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>The minus of this approach is that the debug-session will start with every request. In order not to start it, you have to either remove the <code>XDEBUG_TRIGGER</code> environment variable, or remove <code>debug</code> value from the <code>XDEBUG_MODE</code> environment variable. In the first case you'll still be able to start debug-sessions from your browser with get-parameters or cookies (e.g., with <a href="https://chrome.google.com/webstore/detail/xdebug-helper/eadndfjplgieldjbigjakmdgkmoaaaoc" target="_blank" rel="noopener noreferrer">the Chrome extension</a>).</p>
<p>The <code>extra_hosts</code> setting in the <code>docker-compose.yml</code> config deserves special attention.  It adds the entries specified in this setting to the <code>hosts</code> of the container. In this case it means the domain <code>host.docker.internal</code> should be searched by <code>172.17.0.1</code> IP.  The host machine is usually available exactly at this address, but there are exceptions, so the IP is also configured via an environment variable: so that it can be changed.</p>
<div class="hint-container note">
<p class="hint-container-title">Note</p>
<p>The <code>extra_hosts</code> setting is needed only for Linux, on Windows and MacOS, the <code>host.docker.internal</code> host is available inside the container by default</p>
</div>
<h2>Finished project</h2>
<p>I posted a simple project with the specified settings on github at https://github.com/viktorprogger/xdebug-sample. To check the specified settings work using it, clone it locally and follow these steps:</p>
<ol>
<li>Set up PhpStorm as I <a href="#phpstorm">described above</a></li>
<li>Create a <code>.env</code> file with the following content:<div class="language-dotenv line-numbers-mode" data-highlighter="shiki" data-ext="dotenv" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-dotenv"><span class="line"><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">XDEBUG_MODE</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">=</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">debug</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div></li>
<li>Set a breakpoint in the <code>index.php</code> file</li>
<li>Enable xDebug connection listening in PhpStorm</li>
<li>If you are using Windows or MacOS, remove <code>extra_hosts</code> from <code>docker-compose.yml</code></li>
<li>Run the <code>docker-compose up -d</code> command.  We don't need to start services, but we need to create a network. The next command will not work without it.</li>
<li>Run the <code>docker-compose run --rm php php index.php</code> command. Before the first launch, the container image will be built, this will take a few minutes.</li>
<li>Voila, script execution is stopped at your breakpoint!</li>
</ol>
<p><img src="/xdebug-running.png" alt></p>
]]></content>
    <category term="Development Techniques"/>
    <published>2023-01-21T00:00:00.000Z</published>
  </entry>
</feed>