<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[PHP development blog]]></title><description><![CDATA[php, technical blog, symfony articles. laravel recipes]]></description><link>https://blog.vladverpeta.com</link><generator>RSS for Node</generator><lastBuildDate>Thu, 08 Oct 2026 06:29:26 GMT</lastBuildDate><atom:link href="https://blog.vladverpeta.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Max Attempts Exceeded Exception Horizon 
Laravel]]></title><description><![CDATA[One persistent issue our team faced was managing duplicated events in Laravel's queue system. After numerous attempts to solve it, we finally arrived at a solution that helped us handle duplicated messages without losing critical data.
The Problem: D...]]></description><link>https://blog.vladverpeta.com/max-attempts-exceeded-exception-horizon-laravel</link><guid isPermaLink="true">https://blog.vladverpeta.com/max-attempts-exceeded-exception-horizon-laravel</guid><category><![CDATA[Laravel]]></category><category><![CDATA[Horizon]]></category><category><![CDATA[queue]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Vlad Verpeta]]></dc:creator><pubDate>Sun, 29 Sep 2024 16:17:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727626425890/9a3a3635-2c71-46f8-8086-0a992529095d.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One persistent issue our team faced was managing duplicated events in Laravel's queue system. After numerous attempts to solve it, we finally arrived at a solution that helped us handle duplicated messages without losing critical data.</p>
<h3 id="heading-the-problem-duplicated-messages-in-the-queue">The Problem: Duplicated Messages in the Queue</h3>
<p>Our users often generate activities that trigger multiple events. These events, such as syncing user data with third-party services, get queued for processing. The challenge arises when multiple events with the same key (such as user ID) are added to the queue in quick succession. Without a proper mechanism in place, older events could overwrite newer data, or worse, important data could be lost in third-party systems.</p>
<p>To address this, we initially utilized the <code>WithoutOverlapping</code> feature in Laravel. This ensures that two jobs with the same key do not execute simultaneously. However, we still encountered problems where older jobs were being processed after newer, more relevant ones.</p>
<h3 id="heading-iteration-1-increasing-the-retry-count">Iteration 1: Increasing the Retry Count</h3>
<p>Our first attempt to fix the issue involved increasing the number of retries on the jobs. We thought that by changing the retry count from the default of 3 to 25, we could mitigate the problem, allowing the jobs more chances to succeed.</p>
<pre><code class="lang-php"><span class="hljs-comment">// Increasing retries in the job class</span>
<span class="hljs-keyword">public</span> $tries = <span class="hljs-number">25</span>;
</code></pre>
<p>During testing in our local and development environments, things looked fine. We even conducted load testing, and the system performed well under small loads. However, after deploying the changes to production, the problem returned within a few days. The increased retries only delayed the inevitable and did not solve the root issue.</p>
<h3 id="heading-iteration-2-leveraging-maxexceptions">Iteration 2: Leveraging <code>$maxExceptions</code></h3>
<p>Next, we tried using the <code>$maxExceptions</code> parameter. This parameter controls how many times a job can fail before it is marked as failed. We set it to <code>1</code>, meaning that if a job failed once, it would be immediately retried until there was no other job with the same key currently in progress.</p>
<pre><code class="lang-php"><span class="hljs-comment">// Setting maximum exceptions in the job class</span>
<span class="hljs-keyword">public</span> $maxExceptions = <span class="hljs-number">1</span>;
</code></pre>
<p>While unconventional at first glance, this approach worked remarkably well. The message was re-queued until there were no other conflicting jobs, ensuring we didn't overwrite new data with older data. However, this came with a downside.</p>
<h4 id="heading-pros-and-cons-of-maxexceptions">Pros and Cons of <code>$maxExceptions</code></h4>
<p>By using <code>$maxExceptions</code>, we lost Laravel's native auto-retry functionality. If a message failed, it needed to be manually re-run. Fortunately, this wasn’t a big issue for us because our users generate many such events. Each subsequent message in the queue essentially acts as a retry, ensuring that the most recent data is always processed.</p>
<h3 id="heading-handling-duplicated-events">Handling Duplicated Events</h3>
<p>A key challenge for us was managing the large volume of duplicated events that we didn’t want to process. Laravel provides a <strong>Rate Limiting</strong> feature that allows you to throttle the execution of jobs. However, we couldn’t simply rate-limit all events because we couldn’t afford to skip the final message in the queue. The last event typically signifies that the user has completed all updates, and the latest state needs to be synchronized with third-party services.</p>
<h3 id="heading-the-solution-marking-outdated-messages">The Solution: Marking Outdated Messages</h3>
<p>To overcome this challenge, we implemented a custom middleware: <strong>MarkOutdatedMiddleware</strong>. This middleware checks for the presence of newer messages with the same key in the queue. If such a message exists, the current message is marked as outdated and skipped, ensuring only the latest message is processed.</p>
<p>Here’s the implementation of <code>MarkOutdatedMiddleware</code>:</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MarkOutdatedMiddleware</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">readonly <span class="hljs-keyword">protected</span> <span class="hljs-keyword">string</span> $uniqueId, readonly <span class="hljs-keyword">protected</span> <span class="hljs-keyword">int</span> $dispatchedAt</span>) </span>{}

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params">$job, $next</span>)
    </span>{
        $lastAddedTime = Cache::driver(<span class="hljs-string">'redis'</span>)-&gt;get(<span class="hljs-keyword">$this</span>-&gt;uniqueId);
        <span class="hljs-keyword">if</span> ($lastAddedTime &amp;&amp; $lastAddedTime &gt; <span class="hljs-keyword">$this</span>-&gt;dispatchedAt) {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">return</span> $next($job);
    }
}
</code></pre>
<p>This middleware ensures that only the most recent message for a given key is processed, while older, outdated messages are skipped. This solution fit well with our business logic, where the final state update is the most critical.</p>
<p>Also, below you can see an example of Message Handler class:</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SomeJob</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">ShouldQueue</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">Dispatchable</span>;
    <span class="hljs-keyword">use</span> <span class="hljs-title">InteractsWithQueue</span>;
    <span class="hljs-keyword">use</span> <span class="hljs-title">Queueable</span>;
    <span class="hljs-keyword">use</span> <span class="hljs-title">SerializesModels</span>;

    <span class="hljs-keyword">public</span> $tries = <span class="hljs-number">0</span>;

    <span class="hljs-keyword">public</span> $maxExceptions = <span class="hljs-number">1</span>;

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> $userId;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> $dispatchedAt;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $userId, ?<span class="hljs-keyword">int</span> $dispatchedAt = <span class="hljs-literal">null</span></span>)
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;userId = $userId;
        <span class="hljs-keyword">$this</span>-&gt;onQueue(<span class="hljs-string">'user-queue'</span>);
        <span class="hljs-keyword">$this</span>-&gt;dispatchedAt = $dispatchedAt ?? time();
        Cache::driver(<span class="hljs-string">'redis'</span>)-&gt;put(<span class="hljs-keyword">$this</span>-&gt;uniqueId(), time(), <span class="hljs-number">600</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">middleware</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">return</span> [
            <span class="hljs-keyword">new</span> MarkOutdatedMiddleware(<span class="hljs-keyword">$this</span>-&gt;uniqueId(), <span class="hljs-keyword">$this</span>-&gt;dispatchedAt),
            (<span class="hljs-keyword">new</span> WithoutOverlapping(<span class="hljs-keyword">$this</span>-&gt;uniqueId()))-&gt;releaseAfter(<span class="hljs-number">5</span>)
                -&gt;expireAfter(<span class="hljs-number">30</span>),
        ];
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params">User $user</span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-comment">//Some business logic</span>
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uniqueId</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span>
    </span>{
        <span class="hljs-keyword">return</span> sprintf(<span class="hljs-string">'unique-job-for-user-id-%s'</span>, <span class="hljs-keyword">$this</span>-&gt;getUserId());
    }
}
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>By combining Laravel's <code>WithoutOverlapping</code> feature with a custom middleware solution, we were able to effectively handle duplicated events and avoid processing outdated messages. The result was a more efficient queue system, free from the problems of overwriting or losing data.</p>
<hr />
<h3 id="heading-final-thoughts">Final Thoughts</h3>
<p>This approach may not be suitable for every use case, but it worked well for our high-frequency, user-generated events. If you face similar challenges in managing queues, consider using custom middleware alongside Laravel's existing features.</p>
<p>If you have any questions, feedback, or better solutions, feel free to share them in the comments or reach out directly!</p>
<p>Thank you for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Symfony compiler pass - Strategy pattern: how to write code that is SOLID compatible]]></title><description><![CDATA[We constantly attend job interviews where questions about SOLID principles are mandatory. After that, there are usually a couple of questions about design patterns. The candidate may believe that they have finally found a company where these principl...]]></description><link>https://blog.vladverpeta.com/symfony-compiler-pass-strategy-pattern-how-to-write-code-that-is-solid-compatible</link><guid isPermaLink="true">https://blog.vladverpeta.com/symfony-compiler-pass-strategy-pattern-how-to-write-code-that-is-solid-compatible</guid><category><![CDATA[PHP]]></category><category><![CDATA[clean code]]></category><category><![CDATA[design patterns]]></category><category><![CDATA[StrategyPattern]]></category><category><![CDATA[SOLID principles]]></category><category><![CDATA[Symfony]]></category><dc:creator><![CDATA[Vlad Verpeta]]></dc:creator><pubDate>Sun, 11 Feb 2024 16:05:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/geNNFqfvw48/upload/9842ac9173927c62fe27735a4648b1d6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We constantly attend job interviews where questions about <a target="_blank" href="https://github.com/wataridori/solid-php-example">SOLID principles</a> are mandatory. After that, there are usually a couple of questions about design patterns. The candidate may believe that they have finally found a company where these principles are followed, and they will get to see in practice what they have been reading and hearing about throughout their commercial experience. At the same time, the hiring side may think they have found a candidate who understands good coding practices and will either improve the project's status or, at the very least, not degrade it when writing new functionality.</p>
<blockquote>
<p>P.S.: Of course, this story doesn't apply to 100% or maybe even 90% of developers and companies. However, based on my experience conducting hundreds of interviews and working on around 10 projects, the situation is very similar if not 1:1.</p>
</blockquote>
<p>Today, I propose to explore an example of how to write code that adheres to the following best practices (or at least I would like it to):</p>
<ol>
<li><p>Open-Closed Principle</p>
</li>
<li><p>Dependency Inversion + Dependency Injection</p>
</li>
<li><p>A method must not have more than 5-7 dependencies in the constructor(<a target="_blank" href="https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882">link1</a>, <a target="_blank" href="https://www.amazon.com/Design-Patterns-Object-Oriented-Addison-Wesley-Professional-ebook/dp/B000SEIBB8">link2</a>).</p>
</li>
<li><p>Single Responsibility</p>
</li>
</ol>
<p><strong><em>If you know of any other practices, please add them.</em></strong></p>
<p>Let's consider this question based on a typical task that developers often face, using the example of payment methods.</p>
<h2 id="heading-the-problem-statement">The problem statement</h2>
<p>You're assigned to maintain a project with payment functionality, such as generating reports based on a parameter that comes in the GET request, like <a target="_blank" href="http://pay.com?provider=%7Bpaypal%7D/">https://pay.com?provider={paypal}/</a>. Let's call this parameter "provider," and the implemented payment methods are provider1, provider2, provider3, and provider4.</p>
<p>The current code is implemented, at best, like this:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentReportGenerator</span></span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">$provider1, $provider2, $provider3, $provider4</span>)</span>{
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateReport</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $targetProviderName</span>)</span>{    
        <span class="hljs-keyword">switch</span>($targetProviderName):
        <span class="hljs-keyword">case</span> $provider1::NAME;
            <span class="hljs-keyword">return</span> $provider1-&gt;getReport();
        <span class="hljs-keyword">case</span> $provider2::NAME;
            <span class="hljs-keyword">return</span> $provider2-&gt;getReport();
        <span class="hljs-keyword">case</span> $provider3::NAME;
            <span class="hljs-keyword">return</span> $provider3-&gt;getReport();
        <span class="hljs-keyword">case</span> $provider4::NAME;
            <span class="hljs-keyword">return</span> $provider4-&gt;getReport();
        <span class="hljs-keyword">default</span>:
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \PaymentReportServiceException(<span class="hljs-string">"Wrong provider name given"</span>);
    }
}
</code></pre>
<p>The code is quite good, and often programmers consider it to comply with all SOLID principles.</p>
<p>Now, you receive a new task: the company is expanding to new markets, and you need to add 6 more payment methods, with the possibility of adding more soon.</p>
<p>The developer now faces a challenging choice of how to extend the existing code. I'll try to list typical ways to solve this problem:</p>
<h2 id="heading-1-straightforward-approach"><strong>1) Straightforward approach</strong></h2>
<p>Increase the number of parameters in the constructor and add more cases to the switch statement.<br />This approach might seem like a quick solution, but it tends to violate the Open-Closed Principle, as every time a new provider is added, you need to modify the <code>generateReport</code> function. Additionally, it increases the cyclomatic complexity and makes the code harder to maintain.</p>
<h2 id="heading-2-abandon-dependency-injection"><strong>2) Abandon Dependency Injection</strong></h2>
<p>Dynamically create an instance of the required service based on the incoming value. This typically looks like this:</p>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateReport</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $targetProviderName</span>)</span>{
$className = <span class="hljs-string">'/some/namespace/'</span> . targetProviderName . <span class="hljs-string">'PaymentReportService'</span>;
$methodName = <span class="hljs-string">'getReport'</span>;
$paymentReportService = <span class="hljs-keyword">new</span> $className();

<span class="hljs-keyword">if</span> (class_exists($className)) {
    $instance = <span class="hljs-keyword">new</span> $className();

    <span class="hljs-keyword">if</span> (method_exists($instance, $methodName)) {
        <span class="hljs-keyword">return</span> $instance-&gt;$methodName(<span class="hljs-string">"John"</span>);
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">echo</span> <span class="hljs-string">"Method <span class="hljs-subst">$methodName</span> not found in class <span class="hljs-subst">$className</span>."</span>;
    }
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"Class <span class="hljs-subst">$className</span> not found."</span>;
}
</code></pre>
<p>The drawbacks of this approach are likely apparent to everyone, but I'll mention a few of the most glaring issues:</p>
<ol>
<li><p>All dependencies, if they exist, must be manually passed, and where to get them? Right, again shove them all into the constructor.</p>
</li>
<li><p>If (or when) the signature of the final classes changes, you'll have to come back here and make changes each time.</p>
</li>
<li><p>Not the worst but the most unpleasant - code written this way won't be easily discoverable by an IDE, leading to increased time spent on maintaining this code in the future. <a target="_blank" href="https://en.wikipedia.org/wiki/Ninety%E2%80%93ninety_rule"><mark>According to statistics</mark></a><mark>, programmers spend about 90% of their time reading code and only about 10% writing it. </mark> Thus, such code will incur significant costs for the business due to developer salaries not being insignificant.</p>
</li>
<li><p>Lastly, there's no talk of any inversion of dependencies here at all.</p>
</li>
</ol>
<p>Essentially, we've broken all the benefits that the excellent Symfony framework provides.</p>
<h2 id="heading-3-manual-retrieval-from-the-container">3) Manual retrieval from the container</h2>
<p>Digging into the dependency container object and doing something similar to the previous approach. I won't detail how exactly to do this, but I'll just say that <a target="_blank" href="https://stackoverflow.com/questions/12056178/how-to-access-service-container-in-symfony2-global-helper-function-service?noredirect=1&amp;lq=1">this approach</a> is very close to what we're aiming for. However, we are still deviating from the original idea of how the framework should be used. Also, for this trick, we have to mark our classes as public in services.yaml or directly in the class files, or else they cannot be easily requested from the container. <a target="_blank" href="https://symfony.com/doc/current/service_container/alias_private.html#marking-services-as-public-private">Framework developers consider</a> this action risky, as it can lead to vulnerabilities in your code (for example, if you use third-party libraries with vulnerabilities, an attacker can gain access to the internals of your objects).</p>
<blockquote>
<p>Perhaps you know or have encountered other ways to solve this problem; please write about them. I'm very interested.</p>
</blockquote>
<h2 id="heading-4-proposed-solution">4) Proposed solution</h2>
<blockquote>
<p>I should clarify that the example below is just a specific case of using the functionality of a compiler pass. This example shows that you can do very cool things without writing a multitude of configs and delving into the depths of the framework documentation.</p>
</blockquote>
<ol>
<li><p>We need to create an interface that will unify all our services, for example: <code>IPaymentReportProvider</code>.</p>
</li>
<li><p>Specify that each of our classes implements this interface. It's worth noting that you don't necessarily have to use an interface to mark the necessary set of classes; you can also use tags.</p>
</li>
<li><p>Next, go to services.yaml and add the following lines:</p>
<pre><code class="lang-yaml"> <span class="hljs-attr">services:</span>

     <span class="hljs-comment"># ...other config....</span>

     <span class="hljs-comment">### Describe that our strategy must got list of all objects that implements our target interface</span>
     <span class="hljs-string">App\Service\Payments\PaymentReportGenerator:</span>
       <span class="hljs-attr">arguments:</span> [<span class="hljs-type">!tagged_iterator</span> { <span class="hljs-attr">tag:</span> <span class="hljs-string">'app.payment_report.provider'</span>, <span class="hljs-attr">index_by:</span> <span class="hljs-string">'key'</span> , <span class="hljs-attr">default_index_method:</span> <span class="hljs-string">'getProviderName'</span>}]
   <span class="hljs-comment">#############</span>
     <span class="hljs-attr">_instanceof:</span>
       <span class="hljs-string">App\Service\Payments\ReportProviders\IPaymentReportProvider:</span>
         <span class="hljs-attr">lazy:</span> <span class="hljs-literal">true</span>
         <span class="hljs-attr">tags:</span> [<span class="hljs-string">'app.payment_report.provider'</span>]
</code></pre>
</li>
</ol>
<p>The second block automatically creates, registers, and tags all classes implementing the <code>IPaymentReportProvider</code> interface with the tag - <code>app.payment_report.provider</code>. The <code>lazy: true</code> argument indicates that the classes will be initialized only when explicitly called, and a lightweight proxy object will be used when provided as a list.</p>
<p>The first part declares a service and specifies that one of the parameters to be provided to the object upon creation is a collection of objects tagged with -<code>app.payment_report.provider</code>. Two additional parameters indicate that we want to use a custom key as an index and specify which static method to invoke to get the key value for each element.</p>
<p>In essence, the strategy itself is already implemented using Symfony's features. Now, we need to implement/modify our context class (<a target="_blank" href="https://refactoring.guru/design-patterns/strategy">naming got from here</a>).</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentReportGenerator</span>
</span>{
    <span class="hljs-comment">/**
     * <span class="hljs-doctag">@var</span> IPaymentReportProvider[]
     */</span>
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">array</span> $providers = [];

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">iterable</span> $providers</span>)
    </span>{
        $providers = $providers <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Traversable</span> ? iterator_to_array($providers) : $providers;

        <span class="hljs-keyword">$this</span>-&gt;providers = $providers;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generate</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $paymentProviderName, <span class="hljs-keyword">array</span> $reportOptions</span>):<span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;findPaymentProvider($paymentProviderName)-&gt;generateReport($reportOptions);
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">findPaymentProvider</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $paymentProviderName</span>): <span class="hljs-title">IPaymentReportProvider</span>
    </span>{
        <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">isset</span>(<span class="hljs-keyword">$this</span>-&gt;providers[$paymentProviderName])) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">InvalidArgumentException</span>(sprintf(<span class="hljs-string">' Payment provider for "%s" not found'</span>, $paymentProviderName));
        }
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;providers[$paymentProviderName];
    }
}
</code></pre>
<p>At the moment of object creation, Symfony gathers all classes with the specified tag and injects the collection into the constructor as the variable <code>$providers</code>.</p>
<p>Then, when the client method <code>generate</code> is called, we easily locate the desired provider and initiate the report generation (the object will only be initialized at the moment of calling the <code>generateReport</code> method).</p>
<details><summary>Working example</summary><div data-type="detailsContent">Repository with code - <a target="_blank" href="https://github.com/verpeta/symfony-strategy">link.</a></div></details>

<h2 id="heading-conclusion">Conclusion:</h2>
<p>In this exploration of expanding payment methods in a Symfony project, we delved into various approaches to maintain code flexibility, adhere to SOLID principles, and facilitate future scalability. From direct parameter injection to dynamic service creation, we dissected the pros and cons of each strategy.</p>
<p>The introduction of Symfony features like compiler passes, tags, and lazy loading allowed us to implement a robust solution with minimal code modification, creating a system that dynamically adapts to the inclusion of new payment providers. The chosen strategy not only maintains the integrity of the code but also aligns with Symfony's design philosophy.</p>
<p>As developers, it's crucial to strike a balance between simplicity and extensibility. The discussed techniques not only address the immediate challenge of incorporating additional payment methods but also contribute to building a maintainable and scalable codebase.</p>
<p>Remember, the key to successful software design lies not only in solving current problems but in anticipating and accommodating future changes. By embracing Symfony's features intelligently, developers can create systems that evolve seamlessly, ensuring their projects stay resilient and adaptable in the face of ever-changing requirements.</p>
]]></content:encoded></item><item><title><![CDATA[How to set up: ghost CMS + Docker + Cloudflare without strict ssl enabled]]></title><description><![CDATA[Pre-requirements:

Server with installed some Linux OS(I'm using Debian 11)

Installed docker + docker-compose

Installed Nginx

Domain registered on Cloudflare


Steps to set up:

Cloudflare - almost nothing

Create docker-compose.yaml on your serve...]]></description><link>https://blog.vladverpeta.com/how-to-set-up-ghost-cms-docker-cloudflare-without-strict-ssl-enabled</link><guid isPermaLink="true">https://blog.vladverpeta.com/how-to-set-up-ghost-cms-docker-cloudflare-without-strict-ssl-enabled</guid><category><![CDATA[ghost]]></category><category><![CDATA[cms]]></category><category><![CDATA[Docker]]></category><category><![CDATA[nginx]]></category><dc:creator><![CDATA[Vlad Verpeta]]></dc:creator><pubDate>Sat, 03 Feb 2024 12:07:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706962085781/da9a884f-d471-4b22-a900-7fa812250c19.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-pre-requirements">Pre-requirements:</h3>
<ol>
<li><p>Server with installed some Linux OS(I'm using Debian 11)</p>
</li>
<li><p>Installed docker + docker-compose</p>
</li>
<li><p>Installed Nginx</p>
</li>
<li><p>Domain registered on Cloudflare</p>
</li>
</ol>
<h3 id="heading-steps-to-set-up">Steps to set up:</h3>
<ol>
<li><p>Cloudflare - almost nothing</p>
</li>
<li><p>Create docker-compose.yaml on your server and copy content from here - <a target="_blank" href="https://hub.docker.com/_/ghost/?ref=blog.vladverpeta.com">https://hub.docker.com/_/ghost/</a> (docker-compose.yaml block)</p>
</li>
</ol>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.1'</span>

<span class="hljs-attr">services:</span>

  <span class="hljs-attr">ghost:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">ghost:5-alpine</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">2368</span><span class="hljs-string">:2368</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">database__client:</span> <span class="hljs-string">mysql</span>
      <span class="hljs-attr">database__connection__host:</span> <span class="hljs-string">db</span>
      <span class="hljs-attr">database__connection__user:</span> <span class="hljs-string">root</span>
      <span class="hljs-attr">database__connection__password:</span> <span class="hljs-string">example</span>
      <span class="hljs-attr">database__connection__database:</span> <span class="hljs-string">ghost</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">https://blog.vladverpeta.com</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">ghost:/var/lib/ghost/content</span>

  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mysql:8.0</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">MYSQL_ROOT_PASSWORD:</span> <span class="hljs-string">example</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db:/var/lib/mysql</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">ghost:</span>
  <span class="hljs-attr">db:</span>
</code></pre>
<blockquote>
<p><strong>PS:</strong> Dont forgot setup firewall that will restrict any connections to your server except: 80, 22, 433 ports</p>
</blockquote>
<ol>
<li><p>Run <code>docker-compose up -d</code></p>
</li>
<li><p>Edit/create - <code>/etc/nginx/sites-enabled/{</code><a target="_blank" href="http://your-blog.com"><code>your-blog.com</code></a><code>}</code></p>
</li>
</ol>
<pre><code class="lang-nginx"><span class="hljs-section">server</span> {
    <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;
    <span class="hljs-attribute">server_name</span> blog.vladverpeta.com;

    <span class="hljs-attribute">location</span> / {
        <span class="hljs-attribute">proxy_pass</span> http://localhost:2368;
        <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$http_host</span>;
        <span class="hljs-attribute">proxy_set_header</span> X-Real-IP <span class="hljs-variable">$remote_addr</span>;
        <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-For <span class="hljs-variable">$proxy_add_x_forwarded_for</span>;
        <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-Proto https;
    }
}
</code></pre>
<p><strong>Put eyes on lines 7 and 10, without them, you will have</strong> <code>TOO_MANY_REDIRECTS</code> <strong>error.</strong></p>
<ol>
<li>Run <code>nginx -s reload</code> to apply changes in nginx.</li>
</ol>
<p>That's all, I hope it works for you!)</p>
<p>Don't hesitate to contact me - I'll try to help you🤓</p>
]]></content:encoded></item><item><title><![CDATA[Exchange events between Vue.js & Phaser 3 using Vuex]]></title><description><![CDATA[’ve lost a lot of time struggling to find solution — how to share state between vue.js & Phaser. Currently, I’m interested in learning node.js and decided to do it by developing a browser multiplayer game.
Although I have some experience in Vue.js, s...]]></description><link>https://blog.vladverpeta.com/exchange-events-between-vue-js-phaser-3-using-vuex-6e4502ba857f</link><guid isPermaLink="true">https://blog.vladverpeta.com/exchange-events-between-vue-js-phaser-3-using-vuex-6e4502ba857f</guid><category><![CDATA[Vue.js]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[Vuex]]></category><category><![CDATA[phaser]]></category><dc:creator><![CDATA[Vlad Verpeta]]></dc:creator><pubDate>Sat, 23 Jul 2022 19:25:20 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658605500259/WDx7KAhqe.png" alt="1_Loaq7ctcN-XMtA_TSuKuPg.png" />
’ve lost a lot of time struggling to find solution — how to share state between vue.js &amp; Phaser. Currently, I’m interested in learning node.js and decided to do it by developing a browser multiplayer game.</p>
<p>Although I have some experience in Vue.js, so I decided to use it for some stuff to interact with player and show some game information, like: player auth, displaying game statistics or anything you can imagine)</p>
<p>Because Phaser runs as a standalone application, and you can’t push or pull information from it, you need to use some tricks with third party self-state tools.</p>
<p>I couldn't find how to pass some variable into Phaser through the init process of the game, like event emitter or observer. Also, I found a lot of questions on forums or under YouTube videos — without any response. I decided to write this short article because I hope to help other struggle developers who will search the same feature later.</p>
<p>To integrate Phaser with Vue.js I used:</p>
<ul>
<li><a target="_blank" href="https://github.com/proyecto26/ion-phaser">https://github.com/proyecto26/ion-phaser</a> (<a target="_blank" href="https://vuedose.tips/build-a-game-in-vuejs-with-phaser">Article</a>)</li>
</ul>
<p>Here is my solution how to exchange events &amp; data between vue.js &amp; phaser:</p>
<ol>
<li><strong>Create an empty project</strong>(I made it via <a target="_blank" href="https://cli.vuejs.org/guide/creating-a-project.html">vue-cli</a>)</li>
</ol>
<pre><code>vue create my<span class="hljs-operator">-</span>awesome<span class="hljs-operator">-</span>project  
cd my<span class="hljs-operator">-</span>awesome<span class="hljs-operator">-</span>project  
yarn add phaser vuex @ion<span class="hljs-operator">-</span>phaser<span class="hljs-operator">/</span>core
</code></pre><p>Do not forget to make changes or create <a target="_blank" href="https://github.com/verpeta/phaser-vuejs-vuex/blob/master/vue.config.js">/vue.config.js</a>. Without those configs, you will be unable to load assets (images for our game objects).</p>
<p><strong>2. Integrate phaser into vue.js</strong></p>
<p>Add next 2 lines in your entrypoint(<a target="_blank" href="https://github.com/verpeta/phaser-vuejs-vuex/blob/master/src/main.js">/src/main.js</a>):</p>
<pre><code><span class="hljs-section">import</span> { <span class="hljs-attribute">defineCustomElements</span> as defineI<span class="hljs-literal">on</span>Phaser } from <span class="hljs-string">'<span class="hljs-variable">@ion</span>-phaser/core/loader'</span>

defineI<span class="hljs-literal">on</span>Phaser(window);
</code></pre><p><strong>3. Create Vuex storage:</strong></p>
<p>Add vuex to vue.js app:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-title">store</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'./store'</span>  
....  
....  
<span class="hljs-title">createApp</span>(<span class="hljs-title">App</span>)  
  .<span class="hljs-title">use</span>(<span class="hljs-title">store</span>)  
  .<span class="hljs-title">mount</span>(<span class="hljs-string">'#app'</span>);
</code></pre><p>Create new file — <a target="_blank" href="https://github.com/verpeta/phaser-vuejs-vuex/blob/master/src/store/index.js">/src/store/index.js</a>. Here we have only one variable in state and one mutator to change it state. We will use it in the next step.</p>
<p><strong>4. Create basic game scene:</strong></p>
<p>I'll use code snippet with game logic from — phaser official docs — <a target="_blank" href="https://phaser.io/examples/v3/view/physics/arcade/collision-direction">link</a>.</p>
<p>For it I’ll create game component — “/src/game/Game.vue”</p>
<p>Here we added a block — watch — that will watch on state variable— <strong><em>count</em></strong>. When count equals to 5 — Game ends and popup would show. Surely you will change it to more beautiful decision, with complex logics in your game, it's just example of how to exchange of state.</p>
<p>After that, put game scene(phaser logic) code into a separate file : <a target="_blank" href="https://github.com/verpeta/phaser-vuejs-vuex/blob/master/src/game/scenes/PlayScene.js">/src/game/scenes/PlayScene.js</a></p>
<p>Interesting parts in this file are below:</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-title">store</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"@/store"</span>;


    collectStar(player, star) {  
        star.disableBody(<span class="hljs-literal">true</span>, <span class="hljs-literal">true</span>);  
        store.commit(<span class="hljs-string">'increment'</span>);
    }
</code></pre><p>We import Vuex storage and on star collected— increment value of counter.</p>
<p><strong>4. Get it all together &amp; test:</strong></p>
<p>Run: <code>yarn serve</code></p>
<p>You must see next screen:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658605063991/JCqTGuc-a.png" alt /></p>
<p>Let’s summarize what we did:</p>
<ul>
<li>installed Vuex to our project</li>
<li>created storage file</li>
<li>connected Vuex to Vue.js(main.js)</li>
<li>connected Vuex to Phaser (PlayScene.js)</li>
<li>Listen for changes in Game.vue</li>
</ul>
<p>That's all. Thanks for reading. Here is a <a target="_blank" href="https://github.com/verpeta/phaser-vuejs-vuex">link to repo</a> with code.</p>
<p><strong>PS:</strong> Hope this article was helpful for you. If you have some questions or ideas or just want to code together — <strong>fill free contact with me</strong>:)</p>
]]></content:encoded></item><item><title><![CDATA[Message queue in PHP using Kafka with an ack's]]></title><description><![CDATA[On a new project that I am working on as PHP Tech Lead. The team was faced with the issue of establishing various kinds of decisions.
Including the unification of technologies:

a single type of relational database
a single message broker
a unified f...]]></description><link>https://blog.vladverpeta.com/message-queue-in-php-using-kafka-with-an-acks</link><guid isPermaLink="true">https://blog.vladverpeta.com/message-queue-in-php-using-kafka-with-an-acks</guid><category><![CDATA[PHP]]></category><category><![CDATA[Docker]]></category><category><![CDATA[kafka]]></category><category><![CDATA[queue]]></category><category><![CDATA[message queue]]></category><dc:creator><![CDATA[Vlad Verpeta]]></dc:creator><pubDate>Wed, 28 Jul 2021 20:04:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627327312885/4QpF_iir9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On a new project that I am working on as PHP Tech Lead. The team was faced with the issue of establishing various kinds of decisions.
Including the unification of technologies:</p>
<ul>
<li>a single type of relational database</li>
<li>a single message broker</li>
<li>a unified framework in the context of a programming language</li>
<li>etc.</li>
</ul>
<p>This article will talk about testing Apache Kafka as a message broker.</p>
<p>Kafka is currently already used in the company and this technology is more scalable than the same RabbitMQ, so we are looking in its direction.
Before we start implementing Kafka in our projects, we will test whether this technology covers our needs.</p>
<p>Requirements for a message broker:</p>
<ul>
<li>PHP compatibility</li>
<li>Stable work, fault tolerance, high SLA</li>
<li>Scalability</li>
<li>Fast speed of sending/reading messages to the queue</li>
<li>Confidence in message delivery</li>
<li>Confidence in receiving and processing a message (ack / nack)</li>
</ul>
<p>In fact, all this is already there and works great (at small and medium volumes) for RabbitMQ.
But as I wrote above, we do not want to breed a zoo of technologies.</p>
<h2 id="1-choosing-a-client-for-php">1. Choosing a client for PHP</h2>
<p>The <a target="_blank" href="https://cwiki.apache.org/confluence/display/KAFKA/Clients#Clients-PHP">Kafka official website</a> has several links to the integration repositories:</p>
<ul>
<li><a target="_blank" href="https://github.com/EVODelavega/phpkafka">https://github.com/EVODelavega/phpkafka</a></li>
<li><a target="_blank" href="https://github.com/arnaud-lb/php-rdkafka">https://github.com/arnaud-lb/php-rdkafka</a></li>
<li><a target="_blank" href="https://github.com/quipo/kafka-php">https://github.com/quipo/kafka-php</a></li>
<li><a target="_blank" href="https://github.com/michal-harish/kafka-php">https://github.com/michal-harish/kafka-php</a></li>
</ul>
<p>Only one repository is "live" at the moment and has a lot of stars on GitHub - <a target="_blank" href="https://github.com/arnaud-lb/php-rdkafka">arnaud-lb / php-rdkafka</a>.</p>
<h2 id="2-environment-setup">2. Environment setup</h2>
<p>In the 21st year, Docker is like a whip to Indiana Jones for a developer.</p>
<ol>
<li>I went to hub.docker.com and found <a target="_blank" href="https://hub.docker.com/r/wurstmeister/kafka">the most popular official Kafka image</a>.</li>
<li>Further search in Google for a ready-made example of a docker-compose file with PHP, possibly even with Kafka. And about a surprise - the first line of the search result is <a target="_blank" href="https://github.com/Phillaf/php-kafka-demo">Phillaf / php-kafka-demo</a>. There is already an image from the first paragraph.<pre><code>git <span class="hljs-keyword">clone</span> https:<span class="hljs-comment">//github.com/Phillaf/php-kafka-demo</span>
</code></pre></li>
<li>After cloning the repository, run:<pre><code>docker-compose up -d
</code></pre>From the first try, of course, the project did not come together (at least on my MacOs BigSur).
In order to fix the error, it was enough to simply remove the well-defined version of Kafka.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1627328855382/2MYD9QC3_.png" alt="image.png" /></li>
<li>Everything is ready for the application, but what about without an additional bonus?
In order to better understand what is happening with an unknown tool, I decided to immediately look for a visual manager. There were several paid versions that needed to be installed on a PC. But that's not the true way)
So I installed <a target="_blank" href="https://github.com/provectus/kafka-ui">opensource admin panel</a>.
To do this, add a few lines to our docker-compose.yml<pre><code><span class="hljs-attr">kafka-ui:</span>
 <span class="hljs-attr">image:</span> <span class="hljs-string">provectuslabs/kafka-ui</span>
 <span class="hljs-attr">container_name:</span> <span class="hljs-string">kafka-ui</span>
 <span class="hljs-attr">ports:</span>
   <span class="hljs-bullet">-</span> <span class="hljs-string">"8080:8080"</span>
 <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
 <span class="hljs-attr">environment:</span>
   <span class="hljs-bullet">-</span> <span class="hljs-string">KAFKA_CLUSTERS_0_NAME=local</span>
   <span class="hljs-bullet">-</span> <span class="hljs-string">KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092</span>
   <span class="hljs-bullet">-</span> <span class="hljs-string">KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper:2181</span>
   <span class="hljs-bullet">-</span> <span class="hljs-string">KAFKA_CLUSTERS_0_READONLY=false</span>
</code></pre></li>
<li>Ready, set, start ...<ul>
<li>docker-compose up -d</li>
<li>Go to  <a target="_blank" href="http://localhost">http://localhost</a> - Adds messages to our queue (topic).</li>
<li>Go to <a target="_blank" href="http://localhost:8080">http://localhost:8080</a> - open's our admin panel - where we will see our topic and the first messages in it.</li>
<li>Go into the container (I personally prefer to use docker in phpStorm), but you can use the docker GUI as well as using the console (I will not dwell on this here, I think that those who are already interested in Kafka - will definitely be able to enter the Docker container) and run consumer:<pre><code>php ./<span class="hljs-built_in">public</span>/consumer_low.php
</code></pre>We will see our messages in the console.</li>
</ul>
</li>
</ol>
<p>Congratulations, you are now Kafka adepts.</p>
<h3 id="3-we-adjust-the-configuration-according-to-our-requirements">3. We adjust the configuration according to our requirements.</h3>
<p>As I wrote above, we are interested in full control over messages, our application wants to receive a message exactly 1 time, no more and no less. At the same time, we want to be sure that if in the process of processing a message, the consumer suddenly ends his work (as a result of deployment, an error, or a server crash, ...) then after restarting, this message will be processed, and will not sink into oblivion.</p>
<ol>
<li>To do this, we need to turn off the automatic synchronization of Kafka about the saved offset:<pre><code>$conf-&gt;set(<span class="hljs-string">'enable.auto.commit'</span>, <span class="hljs-string">'false'</span>);
</code></pre></li>
<li>In order for the server to save offset, we must assign a unique identifier to the consumer so that when reconnecting, Kafka knows where we left off:<pre><code>$conf-&gt;set(<span class="hljs-string">'group.id'</span>, <span class="hljs-string">'group_1'</span>);
</code></pre></li>
<li><p>Start listening to the queue:</p>
<pre><code>$topic-&gt;consumeStart($partition, RD_KAFKA_OFFSET_STORED);
</code></pre><p>1st parameter - number of the partition to be read (by default, the topic has the 1st partition and the value of the variable will be = 0).
2nd parameter - number - offset from which to start reading messages.
There are 3 preset modes:</p>
<ul>
<li><em>RD_KAFKA_OFFSET_BEGINNING</em> - start reading from the first message in the partition</li>
<li><em>RD_KAFKA_OFFSET_END</em> - read-only new messages that will appear in the queue only after the consumer is connected</li>
<li><em>RD_KAFKA_OFFSET_STORED</em> - read from where you left off last time (this flag can only be used if - 'group.id' is specified).
<a target="_blank" href="https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md">Here</a>  you can find this and a lot more configuration options.</li>
</ul>
</li>
<li><p>Receiving a message from the queue:</p>
<pre><code>$msg = $topic-&gt;consume($partition, <span class="hljs-number">1000</span>);
</code></pre></li>
<li>After successful processing of the message (saving to the database or etc.), mark the message as read (increment the offset):<pre><code>$topic-&gt;offsetStore($partition, $msg-&gt;offset);
</code></pre></li>
</ol>
<p>Voila, we have implemented consistent work with messages in the queue using Apache Kafka.</p>
<p> <a target="_blank" href="https://github.com/verpeta/php-kafka-intro">Link to the repository.</a> </p>
<p>I would be glad to hear your questions or comments)</p>
]]></content:encoded></item></channel></rss>