<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: php</title>
    <description>The latest articles tagged 'php' on DEV Community.</description>
    <link>https://dev.to/t/php</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/php"/>
    <language>en</language>
    <item>
      <title>I Built a Full Recruitment System in PHP — No Database, No SaaS, No Subscription</title>
      <dc:creator>Palks Studio</dc:creator>
      <pubDate>Fri, 15 May 2026 19:53:59 +0000</pubDate>
      <link>https://dev.to/palks_studio/i-built-a-full-recruitment-system-in-php-no-database-no-saas-no-subscription-ff1</link>
      <guid>https://dev.to/palks_studio/i-built-a-full-recruitment-system-in-php-no-database-no-saas-no-subscription-ff1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj78was76zef2y690mw2i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj78was76zef2y690mw2i.png" alt="CANDIDATE_FLOW recruiter dashboard — applications ranked by matching score"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🇫🇷 Interface currently in French — English version coming soon.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Automatic candidate scoring, recruiter dashboard, multi-campaign management. Deployed on any standard Apache hosting.
&lt;/h2&gt;

&lt;p&gt;After building a complete invoicing system the same way, I applied the same philosophy to recruitment: no SaaS dependency, no monthly subscription, no data leaving the client's server.&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;CANDIDATE_SYSTEM&lt;/strong&gt; — a self-hosted recruitment engine that automatically scores applicants against a job profile, ranks them by matching score, and lets the recruiter focus on the top profiles instead of reading through dozens of CVs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Existing Tools
&lt;/h2&gt;

&lt;p&gt;Most recruitment platforms follow the same model: you pay monthly, your data sits on their servers, and the day you stop paying, everything disappears.&lt;/p&gt;

&lt;p&gt;For small companies, DSIs, and independent recruiters, this makes no sense. They don't need a 300€/month SaaS. They need a simple, reliable tool that works on their own hosting.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The system is split into two parts:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public&lt;/strong&gt; — the application form (&lt;code&gt;/candidat/&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private&lt;/strong&gt; — configuration, scoring engine, candidate data (outside the webroot)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a candidate submits the form, the scoring engine calculates a final score based on three nested levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contribution = scores[answer] × (weight / 100) × (global_weight / 100)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Level 1&lt;/strong&gt; — &lt;code&gt;global_weight&lt;/code&gt;: how much a section counts in the final score.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Level 2&lt;/strong&gt; — &lt;code&gt;weight&lt;/code&gt;: how much a question counts within its section.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Level 3&lt;/strong&gt; — &lt;code&gt;scores&lt;/code&gt;: the raw value assigned to each possible answer (0–100).&lt;/p&gt;

&lt;p&gt;Every candidate gets a score on submission. The dashboard displays them ranked from highest to lowest.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Scoring Engine
&lt;/h2&gt;

&lt;p&gt;The scoring logic lives in a single &lt;code&gt;$coring&lt;/code&gt; class. It reads the campaign configuration files — questions with their weights and scores, and the job profile — then runs the calculation across all sections.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$reponses&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;questions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$score_brut&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getScoreBrut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$reponses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$question&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]]);&lt;/span&gt;
        &lt;span class="nv"&gt;$contribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$score_brut&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$question&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'weight'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$detail&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$question&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'section'&lt;/span&gt;&lt;span class="p"&gt;]][&lt;/span&gt;&lt;span class="s1"&gt;'score_section'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$contribution&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$detail&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$section_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'contribution'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'score_section'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'global_weight'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$score_final&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$section&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'contribution'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Apply penalties&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;profil&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'malus'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$question_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$penalties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$penalties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$reponses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$question_id&lt;/span&gt;&lt;span class="p"&gt;]]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$malus&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nv"&gt;$penalties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$reponses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$question_id&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'score_final'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$score_final&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$malus&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tech stack question (&lt;code&gt;checkbox&lt;/code&gt; type) is scored differently — each selected technology is matched against the scores defined in the question itself. No hardcoded list, fully configurable from the admin interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-Campaign Architecture
&lt;/h2&gt;

&lt;p&gt;Each campaign is completely independent: its own questions, its own job profile, its own candidate data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;campaigns/
└── [slug]/
    ├── config/          → questions, scores, job profile
    └── data/            → applications, uploads, lock file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating a new campaign from the dashboard copies the templates, generates the folder structure, and redirects immediately to the new campaign. One click, ready to use.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;campaign.lock&lt;/code&gt; file controls whether the form is open or closed. The recruiter can close a campaign (which purges all candidate data and sends a closure email to every applicant), then reopen it later from the dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Admin Interface
&lt;/h2&gt;

&lt;p&gt;The recruiter configures everything from the admin panel — no file editing required:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Job profile&lt;/strong&gt; — position title, mission type, location, salary range
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Questions&lt;/strong&gt; — edit labels, answers, scores, section weights; add or delete questions
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Section weights&lt;/strong&gt; — real-time total indicator (must reach 100%)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Penalty rules&lt;/strong&gt; — attach negative scores to specific answers on specific questions
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settings&lt;/strong&gt; — sender name, email, site URL, dashboard password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The section weight total updates in real time as you type. If it doesn't add up to 100%, the indicator turns red.&lt;/p&gt;




&lt;h2&gt;
  
  
  No Database — Just JSON Files
&lt;/h2&gt;

&lt;p&gt;Every application is stored as a flat JSON file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20260514_143000_abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-14 14:30:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score_final"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;74.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score_label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Good fit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"score_detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"terrain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"score_section"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;68.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"contribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"classic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"score_section"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;75.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"contribution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reponses"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trigger_fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No ORM, no migrations, no connection pooling. The dashboard loads all JSON files from the &lt;code&gt;applications/&lt;/code&gt; folder, sorts them by &lt;code&gt;score_final&lt;/code&gt;, and renders the list.&lt;/p&gt;

&lt;p&gt;It's fast enough for the volumes this tool is designed for — typically 20 to 200 applications per campaign.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security
&lt;/h2&gt;

&lt;p&gt;A few things worth noting:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CSRF token&lt;/strong&gt; on the application form — generated per session, validated on submission
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict public/private separation&lt;/strong&gt; — no config file is accessible via the web
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate prevention&lt;/strong&gt; — one email per campaign, checked on every submission
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload validation&lt;/strong&gt; — PDF only, 5 MB max, extension and MIME checked
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP security headers&lt;/strong&gt; on every page (X-Frame-Options, X-Content-Type-Options, Referrer-Policy)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session required&lt;/strong&gt; for dashboard, export, application view, and campaign closure&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conditional Form Fields
&lt;/h2&gt;

&lt;p&gt;Some questions trigger additional fields depending on the answer. For example, answering "Yes" to "Do you have a portfolio?" reveals a required URL field. Answering "Yes" to "Any certifications?" triggers a PDF upload.&lt;/p&gt;

&lt;p&gt;The trigger configuration lives in the questions config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"portfolio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Oui"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"field_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"portfolio_url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"field_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"field_label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Portfolio URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the frontend, a small JS handler watches radio inputs and toggles the conditional field visibility. On the backend, &lt;code&gt;handler.php&lt;/code&gt; only validates the conditional field if the triggering answer was given.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Gets Delivered
&lt;/h2&gt;

&lt;p&gt;When deployed for a client:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full system installed on their hosting
&lt;/li&gt;
&lt;li&gt;First campaign configured with their job profile
&lt;/li&gt;
&lt;li&gt;Admin interface ready to use
&lt;/li&gt;
&lt;li&gt;Technical documentation + user guide
&lt;/li&gt;
&lt;li&gt;Color customization included&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No recurring cost. No dependency on external infrastructure. One flat fee.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PHP 8.x — no framework, no Composer
&lt;/li&gt;
&lt;li&gt;Apache + &lt;code&gt;.htaccess&lt;/code&gt; — URL rewriting, security headers, directory protection
&lt;/li&gt;
&lt;li&gt;Vanilla JS — form validation, clipboard API, accordion UI
&lt;/li&gt;
&lt;li&gt;JSON flat files — zero database&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Palks-Studio/candidat-system" rel="noopener noreferrer"&gt;github.com/Palks-Studio/candidat-system&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Product page: &lt;a href="https://palks-studio.com/en/recruitment-without-saas" rel="noopener noreferrer"&gt;palks-studio.com/en/recruitment-without-saas&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://palks-studio.com" rel="noopener noreferrer"&gt;https://palks-studio.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>architecture</category>
      <category>php</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Lancer une boutique de composants électroniques en 2h chrono avec PrestaShop</title>
      <dc:creator>AKIM SOUILAH</dc:creator>
      <pubDate>Fri, 15 May 2026 19:07:11 +0000</pubDate>
      <link>https://dev.to/webpoint_fr/lancer-une-boutique-de-composants-electroniques-en-2h-chrono-avec-prestashop-3ob5</link>
      <guid>https://dev.to/webpoint_fr/lancer-une-boutique-de-composants-electroniques-en-2h-chrono-avec-prestashop-3ob5</guid>
      <description>&lt;h1&gt;
  
  
  Lancer une boutique de composants électroniques en 2h chrono avec PrestaShop
&lt;/h1&gt;

&lt;p&gt;En tant que dev, j'ai récemment eu besoin de monter rapidement une boutique en ligne pour un client spécialisé dans les composants électroniques. Plutôt que de partir de zéro avec un catalogue à construire manuellement, j'ai découvert une solution qui m'a fait gagner des semaines de travail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le problème des catalogues e-commerce techniques
&lt;/h2&gt;

&lt;p&gt;Quand on monte une boutique spécialisée, notamment dans l'électronique, le vrai calvaire n'est pas l'installation de PrestaShop. C'est plutôt :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;L'intégration de milliers de références produits&lt;/strong&gt; avec leurs fiches techniques détaillées&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La gestion des images produits&lt;/strong&gt; en haute qualité&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Les descriptions techniques&lt;/strong&gt; précises et complètes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Les catégorisations complexes&lt;/strong&gt; (résistances, condensateurs, Arduino, Raspberry Pi...)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Les stocks et tarifications&lt;/strong&gt; cohérentes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour un catalogue de 6000 composants électroniques, on parle facilement de plusieurs semaines de travail fastidieux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Une solution PrestaShop pré-packagée
&lt;/h2&gt;

&lt;p&gt;J'ai trouvé une &lt;a href="https://webpoint.fr/fr/224-boutique-composants-electroniques-prestashop-cle-en-main-velleman.html" rel="noopener noreferrer"&gt;boutique PrestaShop clé en main avec 6000 références Velleman&lt;/a&gt; déjà intégrées. Concrètement, voici ce qui est fourni :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Module PrestaShop installé et configuré&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catalogue complet Velleman&lt;/strong&gt; : composants passifs, actifs, modules Arduino, accessoires&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fiches produits complètes&lt;/strong&gt; avec descriptions, caractéristiques techniques, images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure de catégories&lt;/strong&gt; logique et optimisée&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import automatisé&lt;/strong&gt; via fichiers CSV/XML&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Déploiement technique : les étapes
&lt;/h2&gt;

&lt;p&gt;Le processus est remarquablement simple :&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Prérequis serveur
&lt;/h3&gt;

&lt;p&gt;Assurez-vous d'avoir un environnement PrestaShop fonctionnel :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Vérifier les requirements PHP&lt;/span&gt;
php &lt;span class="nt"&gt;-v&lt;/span&gt;  &lt;span class="c"&gt;# &amp;gt;= 7.2&lt;/span&gt;
php &lt;span class="nt"&gt;-m&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'curl|gd|mbstring|zip|mysql'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Installation du module
&lt;/h3&gt;

&lt;p&gt;Décompressez et uploadez le module via le back-office PrestaShop ou en FTP :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# En ligne de commande si vous avez un accès SSH&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/html/prestashop/modules/
unzip velleman-catalog-module.zip
&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 755 velleman-catalog/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Import du catalogue
&lt;/h3&gt;

&lt;p&gt;Le module propose généralement un script d'import automatisé. Si vous devez l'adapter :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Exemple de personnalisation des imports&lt;/span&gt;
&lt;span class="nv"&gt;$config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'apply_taxes'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'price_multiplier'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Marge personnalisée&lt;/span&gt;
    &lt;span class="s1"&gt;'stock_management'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'default_category'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Personnalisation du thème
&lt;/h3&gt;

&lt;p&gt;Une fois le catalogue importé, personnalisez votre thème pour refléter votre identité :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Couleurs et logo&lt;/li&gt;
&lt;li&gt;Pages institutionnelles&lt;/li&gt;
&lt;li&gt;Menus de navigation&lt;/li&gt;
&lt;li&gt;Options de paiement et livraison&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Les avantages concrets
&lt;/h2&gt;

&lt;p&gt;Après déploiement, voici ce que j'ai constaté :&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Gain de temps massif&lt;/strong&gt; : 2h de setup vs plusieurs semaines&lt;br&gt;
✅ &lt;strong&gt;Données fiables&lt;/strong&gt; : fiches techniques validées par Velleman&lt;br&gt;
✅ &lt;strong&gt;SEO optimisé&lt;/strong&gt; : descriptions uniques et détaillées&lt;br&gt;
✅ &lt;strong&gt;Mise à jour facilitée&lt;/strong&gt; : synchronisation possible avec le catalogue Velleman&lt;br&gt;
✅ &lt;strong&gt;Support technique&lt;/strong&gt; : composants reconnus et documentés&lt;/p&gt;

&lt;p&gt;Pour les devs qui montent des projets e-commerce, c'est exactement le genre de solution qui permet de se concentrer sur la personnalisation et l'expérience utilisateur plutôt que sur la saisie de données.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Si vous devez monter une boutique de composants électroniques, cette approche clé en main vous fera économiser un temps précieux. Parfait pour :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Les agences web qui gèrent des projets e-commerce B2B&lt;/li&gt;
&lt;li&gt;Les revendeurs qui veulent se lancer rapidement&lt;/li&gt;
&lt;li&gt;Les makers spaces et fablabs souhaitant vendre en ligne&lt;/li&gt;
&lt;li&gt;Les développeurs recherchant une base solide à customiser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le catalogue Velleman couvre l'essentiel : Arduino, Raspberry Pi, robotique, composants passifs/actifs, outillage... De quoi démarrer avec une offre professionnelle immédiatement.&lt;/p&gt;

&lt;p&gt;Vous avez déjà utilisé ce type de solution pour accélérer vos projets PrestaShop ? N'hésitez pas à partager votre expérience en commentaire !&lt;/p&gt;

</description>
      <category>prestashop</category>
      <category>ecommerce</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>cURL Converter</title>
      <dc:creator>Codehelper</dc:creator>
      <pubDate>Fri, 15 May 2026 18:10:12 +0000</pubDate>
      <link>https://dev.to/codehelper/curl-converter-38jd</link>
      <guid>https://dev.to/codehelper/curl-converter-38jd</guid>
      <description>&lt;p&gt;🛠️  A new free tool on CodeHelper!&lt;/p&gt;

&lt;p&gt;Convert a curl command into JavaScript fetch, axios, Python requests, PHP cURL, Go net/http, or Node.js code. Parses method, headers, JSON and form bodies, basic auth, and cookies entirely in your browser.&lt;/p&gt;




&lt;p&gt;👉 &lt;a href="https://codehelper.me/tools/curl-converter/" rel="noopener noreferrer"&gt;Try it free on CodeHelper&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>javascript</category>
      <category>python</category>
      <category>php</category>
    </item>
    <item>
      <title>Building a Modular Ecosystem: How I’m Rethinking the CLI for My Personal</title>
      <dc:creator>Benyamin Khalife</dc:creator>
      <pubDate>Fri, 15 May 2026 18:01:04 +0000</pubDate>
      <link>https://dev.to/benkhalife/building-a-modular-ecosystem-how-im-rethinking-the-cli-for-my-personal-4p95</link>
      <guid>https://dev.to/benkhalife/building-a-modular-ecosystem-how-im-rethinking-the-cli-for-my-personal-4p95</guid>
      <description>&lt;p&gt;Every developer has that one project they pour their soul into. For me, it’s &lt;strong&gt;Webrium&lt;/strong&gt; — my personal PHP framework. Recently, I’ve been focusing on its "brain" for command-line operations: the &lt;strong&gt;Webrium Console&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can check out the progress here: &lt;a href="https://github.com/webrium/console" rel="noopener noreferrer"&gt;https://github.com/webrium/console&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vision: Beyond Simple Commands
&lt;/h2&gt;

&lt;p&gt;While I’ve always admired how Laravel’s Artisan simplifies development, I wanted to build something that fits a specific architectural need I had: &lt;strong&gt;True Modularity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal for the Webrium Console wasn't just to run migrations or seeders. I wanted a system where you can build a website normally, and then "export" or "modularize" specific parts of it as independent plugins or components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning Features into Components
&lt;/h2&gt;

&lt;p&gt;The most exciting part of this console library is how it handles site sections. Imagine you’ve built a robust &lt;strong&gt;Admin Panel&lt;/strong&gt; or a &lt;strong&gt;Blog system&lt;/strong&gt;. Instead of having them buried deep within your project’s monolith, Webrium Console allows you to treat them as portable components.&lt;br&gt;
For example:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Blog Component: It’s not just a set of files. It includes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;everything—the management interface for the admin and the public-facing views for the client.&lt;/li&gt;
&lt;li&gt;The Admin Portal: You can build it once and potentially plug it into different projects managed by the framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This "extract and reuse" approach is what I’m currently refining. It’s about being able to say: "I need a blog on this new site," and simply plugging in your pre-built, console-managed component.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I’d love to hear your thoughts on this:&lt;/strong&gt;&lt;br&gt;
Laravel is the industry standard for a reason, but sometimes we seek something different. What is the one thing that would make you look beyond established frameworks like Laravel and try something newer or custom-built for your projects?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do you need a custom website or a specialized web solution?&lt;br&gt;
Feel free to reach out! I’m available for new projects, or even just a free consultation to help you figure out the best tech stack for your needs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s connect:&lt;br&gt;
Telegram: @benyaminir&lt;br&gt;
Email: &lt;a href="mailto:benkhalifedev@gmail.com"&gt;benkhalifedev@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>framework</category>
      <category>opensource</category>
    </item>
    <item>
      <title>153. Find Minimum in Rotated Sorted Array</title>
      <dc:creator>MD ARIFUL HAQUE</dc:creator>
      <pubDate>Fri, 15 May 2026 17:41:30 +0000</pubDate>
      <link>https://dev.to/mdarifulhaque/153-find-minimum-in-rotated-sorted-array-31i1</link>
      <guid>https://dev.to/mdarifulhaque/153-find-minimum-in-rotated-sorted-array-31i1</guid>
      <description>&lt;p&gt;153. Find Minimum in Rotated Sorted Array&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Difficulty:&lt;/strong&gt; Medium&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Topics:&lt;/strong&gt; &lt;code&gt;Array&lt;/code&gt;, &lt;code&gt;Binary Search&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Suppose an array of length &lt;code&gt;n&lt;/code&gt; sorted in ascending order is &lt;strong&gt;rotated&lt;/strong&gt; between &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;n&lt;/code&gt; times. For example, the array &lt;code&gt;nums = [0,1,2,4,5,6,7]&lt;/code&gt; might become:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[4,5,6,7,0,1,2]&lt;/code&gt; if it was rotated &lt;code&gt;4&lt;/code&gt; times.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[0,1,2,4,5,6,7]&lt;/code&gt; if it was rotated &lt;code&gt;7&lt;/code&gt; times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that &lt;strong&gt;rotating&lt;/strong&gt; an array &lt;code&gt;[a[0], a[1], a[2], ..., a[n-1]]&lt;/code&gt; 1 time results in the array &lt;code&gt;[a[n-1], a[0], a[1], a[2], ..., a[n-2]]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Given the sorted rotated array &lt;code&gt;nums&lt;/code&gt; of &lt;strong&gt;unique&lt;/strong&gt; elements, return &lt;em&gt;the minimum element of this array&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You must write an algorithm that runs in &lt;code&gt;O(log n) time&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 1:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input:&lt;/strong&gt; nums = [3,4,5,1,2]&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; 1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explanation:&lt;/strong&gt; The original array was [1,2,3,4,5] rotated 3 times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example 2:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input:&lt;/strong&gt; nums = [4,5,6,7,0,1,2]&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explanation:&lt;/strong&gt; The original array was [0,1,2,4,5,6,7] and it was rotated 4 times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example 3:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input:&lt;/strong&gt; nums = [11,13,15,17]&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; 11&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explanation:&lt;/strong&gt; The original array was [11,13,15,17] and it was rotated 4 times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Constraints:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;n == nums.length&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1 &amp;lt;= n &amp;lt;= 5000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-5000 &amp;lt;= nums[i] &amp;lt;= 5000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;All the integers of &lt;code&gt;nums&lt;/code&gt; are &lt;strong&gt;unique&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nums&lt;/code&gt; is sorted and rotated between &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;n&lt;/code&gt; times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hint:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Array was originally in ascending order. Now that the array is rotated, there would be a point in the array where there is a small deflection from the increasing sequence. eg. The array would be something like [4, 5, 6, 7, 0, 1, 2].&lt;/li&gt;
&lt;li&gt;You can divide the search space into two and see which direction to go. Can you think of an algorithm which has O(logN) search complexity?&lt;/li&gt;
&lt;li&gt;All the elements to the left of inflection point &amp;gt; first element of the array.&lt;/li&gt;
&lt;li&gt;All the elements to the right of inflection point &amp;lt; first element of the array.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This solution finds the minimum element in a rotated sorted array of unique integers using &lt;strong&gt;binary search&lt;/strong&gt; in &lt;strong&gt;O(log n)&lt;/strong&gt; time.&lt;br&gt;&lt;br&gt;
It compares the middle element with the rightmost element to decide whether the minimum lies in the left or right half.&lt;/p&gt;
&lt;h3&gt;
  
  
  Approach:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Binary search on rotated sorted array&lt;/strong&gt; Use &lt;code&gt;left&lt;/code&gt; and &lt;code&gt;right&lt;/code&gt; pointers, compute &lt;code&gt;mid&lt;/code&gt;, and compare &lt;code&gt;nums[mid]&lt;/code&gt; with &lt;code&gt;nums[right]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If &lt;code&gt;nums[mid] &amp;gt; nums[right]&lt;/code&gt;&lt;/strong&gt; The smallest element must be in the right half, so move &lt;code&gt;left&lt;/code&gt; to &lt;code&gt;mid + 1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If &lt;code&gt;nums[mid] &amp;lt;= nums[right]&lt;/code&gt;&lt;/strong&gt; The smallest element is in the left half (including &lt;code&gt;mid&lt;/code&gt;), so move &lt;code&gt;right&lt;/code&gt; to &lt;code&gt;mid&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminate when &lt;code&gt;left == right&lt;/code&gt;&lt;/strong&gt; That position holds the minimum.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's implement this solution in PHP: &lt;strong&gt;&lt;a href="https://github.com/mah-shamim/leet-code-in-php/tree/main/algorithms/000153-find-minimum-in-rotated-sorted-array" rel="noopener noreferrer"&gt;153. Find Minimum in Rotated Sorted Array&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="cd"&gt;/**
 * @param Integer[] $nums
 * @return Integer
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;findMin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$nums&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="mf"&gt;...&lt;/span&gt;
    &lt;span class="mf"&gt;...&lt;/span&gt;
    &lt;span class="mf"&gt;...&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     * go to ./solution.php
     */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Test cases&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;findMin&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                   &lt;span class="c1"&gt;// Output: 1&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;findMin&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// Output: 0&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nf"&gt;findMin&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                 &lt;span class="c1"&gt;// Output: 11&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The array is a sorted array that has been rotated, so it consists of two increasing segments:
&lt;code&gt;[large increasing part, small increasing part]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The minimum is the start of the second segment.&lt;/li&gt;
&lt;li&gt;Comparing &lt;code&gt;mid&lt;/code&gt; with &lt;code&gt;right&lt;/code&gt; helps determine which segment &lt;code&gt;mid&lt;/code&gt; lies in:

&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;mid&lt;/code&gt; &amp;gt; &lt;code&gt;right&lt;/code&gt;, then &lt;code&gt;mid&lt;/code&gt; is in the first larger segment, so min is to the right.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;mid&lt;/code&gt; &amp;lt;= &lt;code&gt;right&lt;/code&gt;, then &lt;code&gt;mid&lt;/code&gt; is in the second smaller segment or the right part, so min is to the left or at &lt;code&gt;mid&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;This comparison works because all elements are unique and sorted, so no ambiguity.&lt;/li&gt;

&lt;li&gt;The loop ends when the search space narrows to one element — the minimum.&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Complexity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time Complexity&lt;/strong&gt;: &lt;em&gt;&lt;strong&gt;O(log n)&lt;/strong&gt;&lt;/em&gt; — each step halves the search space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Space Complexity&lt;/strong&gt;: &lt;em&gt;&lt;strong&gt;O(1)&lt;/strong&gt;&lt;/em&gt; — only a few integer variables used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Contact Links&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you found this series helpful, please consider giving the &lt;strong&gt;&lt;a href="https://github.com/mah-shamim/leet-code-in-php" rel="noopener noreferrer"&gt;repository&lt;/a&gt;&lt;/strong&gt; a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me&lt;a href="https://chaindoorman.com/hzk8jsphf8?key=5ba736283dafd7f94a84865e3cc3d775" rel="noopener noreferrer"&gt;!&lt;/a&gt;&lt;br&gt;
&lt;a href="https://buymeacoffee.com/mah.shamim" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fbuttons%2Fv2%2Fdefault-yellow.png" alt="Buy Me A Coffee" width="545" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want more helpful content like this, feel free to follow me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/in/arifulhaque/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/mah-shamim" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>leetcode</category>
      <category>algorithms</category>
      <category>programming</category>
    </item>
    <item>
      <title>Join &amp; Contribute to FormFlow</title>
      <dc:creator>Vishnu Choudhary</dc:creator>
      <pubDate>Fri, 15 May 2026 12:11:48 +0000</pubDate>
      <link>https://dev.to/vishnuchoudhary/join-contribute-to-formflow-1khh</link>
      <guid>https://dev.to/vishnuchoudhary/join-contribute-to-formflow-1khh</guid>
      <description>&lt;p&gt;🌐 &lt;strong&gt;Join &amp;amp; Contribute to FormFlow&lt;/strong&gt;&lt;br&gt;
We are building FormFlow, a modern and scalable form builder platform for the future of web forms and workflows.&lt;/p&gt;

&lt;p&gt;👉 GitLab Repo:&lt;br&gt;
&lt;a href="https://gitlab.com/vishnu.choudhary/formflow" rel="noopener noreferrer"&gt;https://gitlab.com/vishnu.choudhary/formflow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👨‍💻 &lt;strong&gt;We are looking for contributors who can help in:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧩 Frontend UI improvements&lt;/li&gt;
&lt;li&gt;⚙️ Backend Laravel development&lt;/li&gt;
&lt;li&gt;🐞 Bug fixes &amp;amp; testing&lt;/li&gt;
&lt;li&gt;📊 Dashboard &amp;amp; analytics features&lt;/li&gt;
&lt;li&gt;🔐 Authentication &amp;amp; security improvements&lt;/li&gt;
&lt;li&gt;🚀 Performance optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 &lt;strong&gt;Why contribute?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-world SaaS project experience&lt;/li&gt;
&lt;li&gt;Clean Laravel architecture exposure&lt;/li&gt;
&lt;li&gt;Open-source contribution history&lt;/li&gt;
&lt;li&gt;Opportunity to collaborate on scalable product&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🛠️ &lt;strong&gt;Tech Stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel (PHP)&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;Vite / JS&lt;/li&gt;
&lt;li&gt;Nginx + AWS EC2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📌** How to start**&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fork the repo&lt;/li&gt;
&lt;li&gt;Clone locally&lt;/li&gt;
&lt;li&gt;Pick an issue&lt;/li&gt;
&lt;li&gt;Create a branch&lt;/li&gt;
&lt;li&gt;Submit Pull Request&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;🔥 &lt;strong&gt;Beginners are welcome!&lt;/strong&gt;&lt;br&gt;
If you are learning Laravel or want to improve your backend skills, this project is perfect for hands-on experience.&lt;/p&gt;

&lt;p&gt;🤝 &lt;strong&gt;Let’s build together&lt;/strong&gt;&lt;br&gt;
If you’re interested in contributing, just star ⭐ the repo and start working on issues.&lt;/p&gt;

&lt;p&gt;👉** Repo:** &lt;a href="https://gitlab.com/vishnu.choudhary/formflow" rel="noopener noreferrer"&gt;https://gitlab.com/vishnu.choudhary/formflow&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>opensource</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How We Generate 100+ Product Feeds From 300k SKUs Without Hitting the Database</title>
      <dc:creator>Peter Y</dc:creator>
      <pubDate>Fri, 15 May 2026 11:24:17 +0000</pubDate>
      <link>https://dev.to/flashpeter7/how-we-generate-100-product-feeds-from-300k-skus-without-hitting-the-database-4lem</link>
      <guid>https://dev.to/flashpeter7/how-we-generate-100-product-feeds-from-300k-skus-without-hitting-the-database-4lem</guid>
      <description>&lt;p&gt;Generating product feeds (Google Shopping, Facebook, marketplaces) is a boring problem until your catalog has 300,000 SKUs. Then it becomes a nightmare.&lt;/p&gt;

&lt;p&gt;The naive approach — load each product from PrestaShop, compute its price, check availability, format the output — hits the database with ~80 queries per product. Multiply that by 300k products, add network latency to a clustered database, and you're looking at hours of generation time. Per feed. And we have over a hundred feeds: 10 different feed types × 4 languages × 3 shops.&lt;/p&gt;

&lt;p&gt;We tried the "proper" engineering approach first. It failed. Then I built something dumb and fast that actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Feeds Are Hard in PrestaShop
&lt;/h2&gt;

&lt;p&gt;You can't just dump product data with SQL queries. I mean, you technically can, but you'll regret it.&lt;/p&gt;

&lt;p&gt;PrestaShop computes a lot of things at runtime. Product price depends on specific price rules, group discounts, cart rules, tax rules, country settings, and a dozen admin toggles. Availability depends on stock management mode, pack stock type, combination stock, and warehouse config. Even something simple like "product name" goes through language layers and shop context.&lt;/p&gt;

&lt;p&gt;Reproducing all of that in raw SQL is reverse-engineering the entire PrestaShop business logic layer. Someone might pull it off, but it'll be fragile, it won't respect admin settings, and it'll break on every PrestaShop update.&lt;/p&gt;

&lt;p&gt;So you're stuck loading products through PrestaShop's own objects. And that means PHP, and that means queries. Lots of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Proper" Solution That Failed
&lt;/h2&gt;

&lt;p&gt;We hired a specialist to build a proper feed generation pipeline. His architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Message broker for events&lt;/li&gt;
&lt;li&gt;Enrichment service on Symfony&lt;/li&gt;
&lt;li&gt;Separate PrestaShop instance for data hydration&lt;/li&gt;
&lt;li&gt;Event-driven pipeline with object serialization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five months later: nothing shipped. Not one working feed. The architecture was theoretically sound but practically impossible to finish. We let him go.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Insight
&lt;/h2&gt;

&lt;p&gt;Here's what I realized. We already have a process that loads every product through PrestaShop's full business logic: our product update cron.&lt;/p&gt;

&lt;p&gt;In our setup, product changes arrive from an external source via a Redis queue. A cron job runs every 30 seconds, picks up SKUs that changed, and performs a full product update in PrestaShop. After the update, all the expensive stuff is already computed — prices, stock, categories, attributes, descriptions, tax rules. It's all sitting right there in PHP memory.&lt;/p&gt;

&lt;p&gt;What if, at that exact moment, we just... grabbed it?&lt;/p&gt;

&lt;p&gt;And it gets better. At that same point in the code we're already updating the Elasticsearch search index for the storefront — because the data is the same. So we're already building a product object for search. Adding a second write for feed data is almost free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Capture Once, Feed Forever
&lt;/h2&gt;

&lt;p&gt;During the product update cycle, after all business logic has executed, we collect every field that any feed might ever need into a single associative array. Price, stock, description, categories, attributes, images, EAN, weight, shipping — everything.&lt;/p&gt;

&lt;p&gt;Then we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;json_encode&lt;/code&gt; it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gzencode&lt;/code&gt; it (compress)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;base64_encode&lt;/code&gt; it (for safe storage)&lt;/li&gt;
&lt;li&gt;Write it to Elasticsearch as one document per SKU&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Elasticsearch index is dead simple: SKU as the ID, compressed payload, a timestamp, and an "indexed" flag. One document per product. ~4KB per document compressed. The whole index for 300k products is about 4GB.&lt;/p&gt;

&lt;p&gt;No extra database queries. No separate pipeline. The data piggybacks on a process that was already running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feed Generation: Just Scroll and Write
&lt;/h2&gt;

&lt;p&gt;When it's time to generate feeds, the process is trivial:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open an Elasticsearch scroll query over the entire index&lt;/li&gt;
&lt;li&gt;For each document: decompress → json_decode → you have all product data&lt;/li&gt;
&lt;li&gt;Write to whatever format the feed needs (CSV, XML, JSON)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key trick: we write all languages and all shops in a single pass. One scroll through 300k documents, and we're writing to all output files simultaneously. No need to iterate the catalog multiple times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Numbers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;216,000 active SKUs&lt;/li&gt;
&lt;li&gt;One feed (all languages, all shops): &lt;strong&gt;6 minutes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;All feeds combined: &lt;strong&gt;~35 minutes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Write speed: ~500 SKUs/second&lt;/li&gt;
&lt;li&gt;Database load during feed generation: &lt;strong&gt;zero&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For comparison: the naive approach (load each product via PrestaShop objects) would need ~80 queries per SKU. That's 24 million queries for one feed. On a clustered database with network latency — we're talking hours per feed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding New Feeds Takes Minutes
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most happy with.&lt;/p&gt;

&lt;p&gt;The compressed JSON in Elasticsearch contains a superset of all fields any feed could need. When a manager says "we need a new feed for marketplace X," I don't build a new data pipeline. I just:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look at what fields the marketplace requires&lt;/li&gt;
&lt;li&gt;Check if they're in the universal object (they usually are)&lt;/li&gt;
&lt;li&gt;Write a simple transformer: read field A, format it as column B&lt;/li&gt;
&lt;li&gt;Add it to the feed generation cron&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A new feed type takes maybe an hour. Most of that is reading the marketplace's spec.&lt;/p&gt;

&lt;p&gt;If a required field isn't in the universal object yet — I add it in the product update step, wait for one full update cycle, and it's available everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works (And What It's Actually Called)
&lt;/h2&gt;

&lt;p&gt;After building this, I looked up whether the approach has a name. Turns out it's a combination of two well-known patterns — I just didn't know that when I built it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Materialized View&lt;/strong&gt; — a precomputed, stored result of a complex query, optimized for reading. That's exactly what our compressed JSON in Elasticsearch is: a materialized view of PrestaShop's business logic output. The classic version lives in a database (PostgreSQL has them built-in, for example). Ours lives in Elasticsearch because the "query" isn't SQL — it's the result of PHP-level computations that can't be expressed in SQL at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event-Carried State Transfer&lt;/strong&gt; — a pattern from event-driven architecture where instead of telling consumers "something changed, go fetch the data yourself," you send the full state along with the event. That's exactly what we do: when a product updates, we don't just flag it for later processing. We capture the complete product snapshot right there and store it. Feed generators never need to go back to the source.&lt;/p&gt;

&lt;p&gt;The twist is that both patterns are usually discussed in the context of microservices and distributed systems. Nobody talks about applying them inside a PHP monolith to solve a feed generation problem. But that's what works.&lt;/p&gt;

&lt;p&gt;The insight isn't architectural theory. It's practical: &lt;strong&gt;don't go get data when you can grab it while it's already in your hands.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;p&gt;It's not perfect.&lt;/p&gt;

&lt;p&gt;Feed data is only as fresh as the last product update cycle. If a product updates at 10:00 and feeds generate at 10:30, there's a 30-minute gap. For our B2B use case this is fine. For a flash-sale store it might not be.&lt;/p&gt;

&lt;p&gt;The universal object can get bloated. Ours has maybe 40-50 fields per product. Not terrible, but it needs occasional cleanup.&lt;/p&gt;

&lt;p&gt;You need the product update pipeline to begin with. If your products are edited manually in PrestaShop admin — this approach doesn't apply directly. You'd need to hook into PrestaShop's save events instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don't generate feeds by loading products from the database. At scale, it's impossibly slow.&lt;/li&gt;
&lt;li&gt;Don't build a separate data pipeline. It's months of work and probably won't ship.&lt;/li&gt;
&lt;li&gt;Capture product data during your existing update process, when all business logic has already executed.&lt;/li&gt;
&lt;li&gt;Store it compressed in Elasticsearch. One document per SKU, ~4KB each.&lt;/li&gt;
&lt;li&gt;Generate feeds by scrolling Elasticsearch and writing files. Zero database load, 500 SKUs/second.&lt;/li&gt;
&lt;li&gt;New feed types take an hour to add, not weeks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes the best architecture is no architecture. Just write stuff down when you already have it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: prestashop, ecommerce, elasticsearch, performance&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>performance</category>
      <category>php</category>
    </item>
    <item>
      <title>Laravel MCP Implementation Cost: What Companies Should Budget in 2026</title>
      <dc:creator>Dhruvil Joshi</dc:creator>
      <pubDate>Fri, 15 May 2026 09:43:46 +0000</pubDate>
      <link>https://dev.to/dhruvil_joshi14/laravel-mcp-implementation-cost-what-companies-should-budget-in-2026-302g</link>
      <guid>https://dev.to/dhruvil_joshi14/laravel-mcp-implementation-cost-what-companies-should-budget-in-2026-302g</guid>
      <description>&lt;p&gt;I have spent the last six months shipping a Laravel MCP server to production for an internal AI assistant. The install was trivial. The bill was not. If you are a CTO or engineering lead scoping an MCP integration on top of Laravel, this is the honest Laravel MCP implementation cost breakdown I wish someone had handed me on day one.&lt;/p&gt;

&lt;p&gt;Quick context if you're new to MCP: it's the open protocol Anthropic released in November 2024 that lets AI clients talk to your application through a standardized JSON-RPC interface. As of &lt;a href="https://dev.to/x4nent/complete-guide-to-mcp-model-context-protocol-in-2026-architecture-implementation-and-4a11"&gt;March 2026, MCP hit 97 million monthly SDK downloads and 81,000+ GitHub stars&lt;/a&gt;, with every major AI vendor on board. Laravel's official MCP package launched a public beta in September 2025, and production builds are now happening across fintech, healthcare, and SaaS. Here's where the money actually goes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Laravel MCP Implementation Cost Doesn't Look Like a Standard API Build
&lt;/h2&gt;

&lt;p&gt;A normal Laravel API build is predictable. You estimate engineering hours, factor in cloud and database, add a 20% buffer, and the number usually holds. &lt;a href="https://www.bacancytechnology.com/blog/laravel-mcp-server" rel="noopener noreferrer"&gt;Laravel MCP&lt;/a&gt; doesn't follow that pattern. The protocol layer, the auth model, the transport choice, and the AI client testing matrix all introduce cost surfaces that don't exist in a standard Laravel app.&lt;/p&gt;

&lt;p&gt;The package itself is free. composer require laravel/mcp gets you the foundation. Every other line in the budget is where the real Laravel MCP implementation cost lives, and most of those lines don't show up until you're three weeks into the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two-Minute Install &amp;amp; Why That's Misleading
&lt;/h2&gt;

&lt;p&gt;Here's the entire install. It really is this fast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require laravel/mcp

php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ai-routes

php artisan make:mcp-server WeatherServer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two minutes to a working MCP server scaffold. Anyone who quoted you the Laravel MCP implementation cost based on this part is wildly off. The real cost starts at the moment you have to design tools, resources, prompts, choose your auth model, decide on a transport, and test against three different AI clients. That work begins after this scaffold.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 7 Cost Categories That Define Laravel MCP Implementation Cost in 2026
&lt;/h2&gt;

&lt;p&gt;Below are the seven line items I tracked across the project, with real hour ranges and dollar figures at typical mid-market contract rates ($100–$150/hr blended). Add them together, and you get the realistic Laravel MCP implementation cost for a production-grade build.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Server Architecture and Tool Design
&lt;/h3&gt;

&lt;p&gt;The single biggest expense in any Laravel MCP build is the architectural decision phase.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stateless or stateful?&lt;/li&gt;
&lt;li&gt;How granular should your tools be?&lt;/li&gt;
&lt;li&gt;One tool per database table, or higher-level workflow tools?&lt;/li&gt;
&lt;li&gt;Which resources do you expose, and which stay internal?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This phase eats 60 to 120 engineering hours on a real build, which translates to roughly $6,000 to $15,000 of total Laravel MCP implementation cost. Skip this, and you'll rebuild half your tools six weeks in once you realize the AI client is confused about which tool to call.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. OAuth 2.1 and Authentication Setup
&lt;/h3&gt;

&lt;p&gt;OAuth 2.1 is the documented authentication mechanism for MCP. Laravel's docs recommend Passport for the cleanest path. If you're already on Sanctum, retrofitting Passport adds 20 to 40 hours. You can stay on Sanctum and have it work with most AI clients, but the moment a client requires OAuth, you're in for a migration. Budget $2,500 to $5,000 here, more if your existing auth is heavily customized. The auth choice is one of the most underestimated drivers of total Laravel MCP implementation cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Tool, Resource, and Prompt Implementation
&lt;/h3&gt;

&lt;p&gt;Each MCP tool takes 4 to 8 hours from design to test. A real production server ships 8 to 15 tools, putting this line item at $3,000 to $10,000. Here's what a real Laravel MCP tool looks like in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Mcp\Tools&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Mcp\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Mcp\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Mcp\Server\Tool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateInvoiceTool&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Tool&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="s1"&gt;'Create a new invoice for a given customer.'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$customerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'customer_id'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Invoice&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'customer_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'amount'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'draft'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;"Invoice &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$invoice&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; created for ${$amount}"&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks simple. The hidden cost is in the schema design, input validation, and the testing matrix against different AI clients that each interpret the tool description slightly differently.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Streamable HTTP Transport Setup
&lt;/h3&gt;

&lt;p&gt;If you're running stdio (local MCP server consumed by Claude Desktop or Claude Code), you skip this. If you're running a remote MCP server, which most production builds need, you're on Streamable HTTP. Setting that up properly with load testing, session management, and proper SSE fallback for older clients adds 30 to 50 hours. Roughly $3,500 to $7,500 added to your Laravel MCP implementation cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. AI Client Compatibility Testing
&lt;/h3&gt;

&lt;p&gt;Claude, ChatGPT, and Cursor all speak MCP, but they don't all behave identically. Tool descriptions that work perfectly in Claude get ignored in ChatGPT. Resource URI templates that Cursor handles natively confuse Claude Desktop. Real client testing takes 25 to 40 hours, which most teams don't budget for at all. This is the single biggest reason teams that try to &lt;a href="https://www.bacancytechnology.com/hire-laravel-developer" rel="noopener noreferrer"&gt;hire Laravel developers&lt;/a&gt; without MCP experience end up over budget. Engineers familiar with the protocol from day one cut this testing phase roughly in half.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Security Hardening and Audit Logging
&lt;/h3&gt;

&lt;p&gt;PII sanitization in tool inputs, audit trails for every tool invocation, sandbox isolation for untrusted MCP servers, and strict server-side schema validation. These are non-optional in 2026, especially for healthcare, fintech, or any compliance-heavy domain. If you ship without them, you'll retrofit later at 3 to 5 times the cost. Budget $4,000 to $8,000 upfront. The Laravel MCP implementation cost for security retrofits in regulated industries climbs fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Ongoing Maintenance and Spec Drift
&lt;/h3&gt;

&lt;p&gt;The MCP spec is still evolving. Stateless server operation, MCP Apps (interactive HTML rendered in AI clients), and the upcoming A2A (Agent-to-Agent) protocol all land in 2026. Plan for 5 to 10 engineering hours per month minimum to stay current with the spec, plus another 5 to 10 hours of bug fixes and AI client compatibility updates. Roughly $1,500 to $3,000 per month, indefinitely. This recurring line is the Laravel MCP implementation cost that most teams forget to budget for past year one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Budget Ranges for Production Builds in 2026
&lt;/h2&gt;

&lt;p&gt;Stitching all seven line items together, here are the three scenarios I see most often in production builds today. Each tier represents a real Laravel MCP implementation cost band based on what teams are actually shipping in 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Build Cost&lt;/th&gt;
&lt;th&gt;Monthly Run&lt;/th&gt;
&lt;th&gt;Typical Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Small (internal tool)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$15K – $25K&lt;/td&gt;
&lt;td&gt;~$500&lt;/td&gt;
&lt;td&gt;3–5 tools, single client, stdio transport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mid-size (customer-facing)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$40K – $75K&lt;/td&gt;
&lt;td&gt;~$1500&lt;/td&gt;
&lt;td&gt;8–15 tools, OAuth 2.1, Streamable HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enterprise (multi-server)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$100K – $200K+&lt;/td&gt;
&lt;td&gt;~$5,000+&lt;/td&gt;
&lt;td&gt;A2A-ready, audit-compliant, multi-tenant&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The jump between tiers isn't linear because each tier adds an architectural axis. Mid-size adds OAuth and remote transport. Enterprise adds multi-tenancy, A2A readiness, and audit compliance. Any vendor quoting you a flat number without asking which tier you're targeting is guessing at the Laravel MCP implementation cost, not estimating it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Skip and Where I'd Over-Invest Next Time
&lt;/h2&gt;

&lt;p&gt;Honest retrospective on what I'd do differently to lower the Laravel MCP implementation cost on a second build.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skip:&lt;/strong&gt; Trying to support both stdio and Streamable HTTP for v1. Pick one. You'll save 40–60 hours and zero customer care.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip:&lt;/strong&gt; Building custom OAuth before trying Passport with the built-in mcp:use scope. The Laravel docs recommend this for a reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-invest in:&lt;/strong&gt; Audit logging from day one. AI agent action compliance requirements tightened across H2 2026, and retrofitting logs into a production server is painful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over-invest in:&lt;/strong&gt; AI client compatibility testing. Every client behaves differently. The 25–40 hours here save you from production bug reports that are agonizing to debug.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Take Before You Greenlight the Build
&lt;/h2&gt;

&lt;p&gt;Laravel MCP is one of the most exciting things to land in the Laravel ecosystem in years. The package is free, the install is trivial, and the protocol genuinely lives up to the “USB-C for AI” branding. The Laravel MCP implementation cost lives in the architectural decisions, the auth model, the transport choice, and the AI client testing matrix. Anyone quoting a flat number without asking about all four is guessing. If you're scoping a serious build, partner with &lt;a href="https://www.bacancytechnology.com/laravel-development" rel="noopener noreferrer"&gt;Laravel development services&lt;/a&gt; teams that have shipped MCP servers before and know where the hours actually go.&lt;/p&gt;

&lt;p&gt;Curious what others have spent. If you've shipped a Laravel MCP server in production, drop your numbers in the comments. I'll update the article with reader data once I have a few responses.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>laravel</category>
      <category>mcp</category>
      <category>php</category>
    </item>
    <item>
      <title>Magento 2 Database Deadlocks: Causes, Detection &amp; Prevention</title>
      <dc:creator>Magevanta</dc:creator>
      <pubDate>Fri, 15 May 2026 09:02:23 +0000</pubDate>
      <link>https://dev.to/magevanta/magento-2-database-deadlocks-causes-detection-prevention-1856</link>
      <guid>https://dev.to/magevanta/magento-2-database-deadlocks-causes-detection-prevention-1856</guid>
      <description>&lt;p&gt;Database deadlocks are one of those production issues that don't announce themselves with a flashy error page — they hide in your MySQL slow query log, surface as mysterious 500 errors during peak traffic, and leave your team scratching their heads at 2 AM. If you're running Magento 2 at scale, deadlocks are not a question of &lt;em&gt;if&lt;/em&gt;, but &lt;em&gt;when&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This guide covers the full picture: why deadlocks happen in Magento specifically, how to detect them before they cause real damage, and concrete prevention strategies you can implement today.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Database Deadlock?
&lt;/h2&gt;

&lt;p&gt;A deadlock occurs when two or more transactions are each waiting for the other to release a lock, creating a circular dependency that can never resolve on its own. MySQL's InnoDB engine detects this situation and automatically rolls back one of the transactions — the "victim" — returning a &lt;code&gt;Deadlock found when trying to get lock; try restarting transaction&lt;/code&gt; error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; 
try restarting transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Magento will log these and, if retry logic is in place, silently retry. But under high concurrency, deadlocks stack up fast and degrade the entire checkout flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Magento 2 Is Especially Prone to Deadlocks
&lt;/h2&gt;

&lt;p&gt;Magento's architecture involves several high-concurrency write patterns that are classic deadlock recipes:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Inventory Reservation During Checkout
&lt;/h3&gt;

&lt;p&gt;When multiple customers checkout concurrently with overlapping cart items, Magento locks inventory rows in &lt;code&gt;inventory_reservation&lt;/code&gt; and &lt;code&gt;cataloginventory_stock_item&lt;/code&gt;. If two transactions lock the rows in different orders — which Magento's parallel processing easily triggers — you get a deadlock.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Quote and Order Tables
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;quote&lt;/code&gt;, &lt;code&gt;quote_item&lt;/code&gt;, &lt;code&gt;sales_order&lt;/code&gt;, and &lt;code&gt;sales_order_item&lt;/code&gt; tables are constantly written during the checkout flow. Magento updates totals, applies rules, reserves stock, and generates orders — all in heavily nested transactions. These tables see high lock contention during flash sales or email campaigns.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. EAV Attribute Updates
&lt;/h3&gt;

&lt;p&gt;The EAV tables (&lt;code&gt;catalog_product_entity_*&lt;/code&gt;, &lt;code&gt;customer_entity_*&lt;/code&gt;) use multi-row inserts and updates. Under concurrent import or mass update jobs running alongside regular traffic, these tables frequently deadlock.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Indexer Runs During Business Hours
&lt;/h3&gt;

&lt;p&gt;Running indexers (especially &lt;code&gt;catalog_product_price&lt;/code&gt; or &lt;code&gt;catalogrule_rule&lt;/code&gt;) while the storefront is serving traffic creates massive lock contention. Indexers can lock entire index tables while customer-facing queries try to read them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting Deadlocks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Check the InnoDB Status
&lt;/h3&gt;

&lt;p&gt;The most direct way to see recent deadlocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt; &lt;span class="n"&gt;INNODB&lt;/span&gt; &lt;span class="n"&gt;STATUS&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for the &lt;code&gt;LATEST DETECTED DEADLOCK&lt;/code&gt; section. It will show you the exact transactions involved, which tables and rows were locked, and which transaction was rolled back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable the InnoDB Deadlock Log
&lt;/h3&gt;

&lt;p&gt;For persistent logging, add this to your &lt;code&gt;my.cnf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mysqld]&lt;/span&gt;
&lt;span class="py"&gt;innodb_print_all_deadlocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;ON&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes every deadlock to the MySQL error log (&lt;code&gt;/var/log/mysql/error.log&lt;/code&gt;), giving you a historical record to analyze patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitor with Performance Schema
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;performance_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events_errors_summary_global_by_error&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;error_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ER_LOCK_DEADLOCK'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows the cumulative deadlock count since MySQL started — useful for baselining and alerting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Magento Exception Log
&lt;/h3&gt;

&lt;p&gt;Deadlocks that Magento doesn't retry successfully will end up in &lt;code&gt;var/log/exception.log&lt;/code&gt;. Filter for &lt;code&gt;1213&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"1213"&lt;/span&gt; var/log/exception.log | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're seeing more than a handful per hour during peak traffic, you have a real problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevention Strategies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Reduce Transaction Scope
&lt;/h3&gt;

&lt;p&gt;The longer a transaction holds locks, the higher the chance of a deadlock. Review custom code and plugins that wrap large operations in single transactions. Split them into smaller, targeted transactions where possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bad: one big transaction&lt;/span&gt;
&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;transactionFactory&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stockItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$priceRule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Better: separate, focused saves&lt;/span&gt;
&lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$stockItem&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Consistent Lock Ordering
&lt;/h3&gt;

&lt;p&gt;Deadlocks often happen because two transactions acquire the same locks in different orders. If you have custom code that locks multiple rows or tables, ensure all code paths always acquire locks in the same order (e.g., always lock by entity ID ascending).&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; Sparingly
&lt;/h3&gt;

&lt;p&gt;Magento (and many third-party modules) overuse &lt;code&gt;SELECT FOR UPDATE&lt;/code&gt;. This pessimistic locking is often unnecessary. Consider whether optimistic locking — check-then-update with a version column — is sufficient for your use case.&lt;/p&gt;

&lt;p&gt;For inventory specifically, Magento 2.3+ introduced the &lt;strong&gt;Inventory Reservation&lt;/strong&gt; pattern (&lt;code&gt;inventory_reservation&lt;/code&gt; table with append-only inserts) precisely to reduce lock contention. Make sure you're on Magento 2.3+ MSI and not using legacy &lt;code&gt;CatalogInventory&lt;/code&gt; where avoidable.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Isolate Indexer Runs
&lt;/h3&gt;

&lt;p&gt;Schedule all indexers to run during off-peak hours. Even better, switch from full reindex to &lt;strong&gt;incremental (realtime) indexing&lt;/strong&gt; for most indexers — this spreads the write load over time instead of creating a burst:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php bin/magento indexer:set-mode schedule catalog_product_price catalogrule_rule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For indexers that must run in batch, use a maintenance window and disable your load balancer from sending traffic during that period.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Tune InnoDB Lock Wait Timeout
&lt;/h3&gt;

&lt;p&gt;By default, InnoDB waits 50 seconds before giving up on a lock. That's too long for a web request. Tune it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mysqld]&lt;/span&gt;
&lt;span class="py"&gt;innodb_lock_wait_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This causes deadlock victims to fail faster, reducing the cascade effect on your web tier. Magento's retry logic will handle most of these gracefully.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Switch to READ COMMITTED Isolation
&lt;/h3&gt;

&lt;p&gt;InnoDB's &lt;code&gt;REPEATABLE READ&lt;/code&gt; isolation level (Magento's default) uses gap locks, which increase deadlock risk. Switching to &lt;code&gt;READ COMMITTED&lt;/code&gt; reduces gap locking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mysqld]&lt;/span&gt;
&lt;span class="py"&gt;transaction_isolation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;READ-COMMITTED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is safe for most Magento workloads and is often recommended in high-traffic setups. Test thoroughly in staging first — some edge cases in custom code may rely on the stricter isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Connection Pooling
&lt;/h3&gt;

&lt;p&gt;Too many simultaneous database connections increase the probability of lock contention. Use a connection pooler like &lt;strong&gt;ProxySQL&lt;/strong&gt; or tune &lt;code&gt;max_connections&lt;/code&gt; alongside PHP-FPM pool sizes to prevent connection storms during traffic spikes. See our &lt;a href="https://dev.to/blog/magento-2-database-connection-pooling"&gt;Database Connection Pooling guide&lt;/a&gt; for details.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Audit Third-Party Modules
&lt;/h3&gt;

&lt;p&gt;Many deadlocks originate in poorly written third-party modules that use direct SQL writes, lock entire tables, or run in hooks that fire during transactions. Use the InnoDB status output to identify which tables are involved in your deadlocks, then audit which modules write to those tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Find tables with high lock wait counts&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;object_schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;object_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count_read_with_shared_locks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;count_write_allow_write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sum_timer_wait&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;performance_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;table_lock_waits_summary_by_table&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sum_timer_wait&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling Deadlocks in Custom Code
&lt;/h2&gt;

&lt;p&gt;If you're building custom functionality that writes to Magento's core tables, implement retry logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$maxRetries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;doDatabaseWork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;\Zend_Db_Statement_Exception&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;'1213'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$maxRetries&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nb"&gt;usleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$attempt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// exponential backoff&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exponential backoff is important — retrying immediately often just causes another deadlock with the same competing transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Deadlocks in Magento 2 are manageable once you know where to look. The key takeaways:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Enable &lt;code&gt;innodb_print_all_deadlocks&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Visibility into deadlock patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schedule indexers off-peak&lt;/td&gt;
&lt;td&gt;Reduces lock contention significantly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch indexers to &lt;code&gt;schedule&lt;/code&gt; mode&lt;/td&gt;
&lt;td&gt;Spreads write load over time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set &lt;code&gt;transaction_isolation = READ-COMMITTED&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Reduces gap locks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tune &lt;code&gt;innodb_lock_wait_timeout = 10&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Faster failure, less cascade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit third-party modules&lt;/td&gt;
&lt;td&gt;Often the root cause&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Start with visibility — enable logging, identify your most frequent deadlock tables, then apply targeted fixes. In most cases, a combination of indexer scheduling and isolation level tuning resolves 80% of the deadlock volume. For the remainder, it's usually a specific module or custom code path that needs attention.&lt;/p&gt;

&lt;p&gt;Don't let deadlocks silently degrade your checkout. The data is all there in MySQL — you just need to look.&lt;/p&gt;

</description>
      <category>magento</category>
      <category>mysql</category>
      <category>performance</category>
      <category>php</category>
    </item>
    <item>
      <title>Asset Mapper vs. 20-Year-Old Legacy Code: The Ultimate Boss Fight</title>
      <dc:creator>Jozef Môstka</dc:creator>
      <pubDate>Fri, 15 May 2026 07:16:49 +0000</pubDate>
      <link>https://dev.to/tito10047/asset-mapper-vs-20-year-old-legacy-code-the-ultimate-boss-fight-1bc2</link>
      <guid>https://dev.to/tito10047/asset-mapper-vs-20-year-old-legacy-code-the-ultimate-boss-fight-1bc2</guid>
      <description>&lt;p&gt;Yes, we all understand the necessity of developing systems using cutting-edge technologies. But honestly, legacy cases like this are still among us. I will be really glad if you drop a comment letting us know that you understand our struggle and are fighting with something similar!&lt;/p&gt;

&lt;p&gt;Picture this: you have a massive system running on something that only IT dinosaurs and a few museum exhibits remember today – yes, I’m talking about good old Classic ASP.&lt;/p&gt;

&lt;p&gt;Eight years ago, our company said, "We can't go on like this!" and we committed to a heroic deed: migrating to modern Symfony. However, since our system is massive and capacities are limited, a complete rewrite all at once would have meant a pure disaster (and probably mass resignations).&lt;/p&gt;

&lt;p&gt;So, we chose the method of gradual slicing. We develop new features cleanly in Symfony, while keeping the old ASP code alive. As a reward, every programmer was given one day a week dedicated to sacred refactoring.&lt;/p&gt;

&lt;p&gt;Sounds like a great plan, right? I agree. Until you hit the reality of two completely different worlds coexisting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: How to Make Symfony and ASP Live Under One (Directory) Roof?
&lt;/h2&gt;

&lt;p&gt;The structure of our project was, let's say, a bit exotic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ASP code sits right in the &lt;code&gt;root&lt;/code&gt; directory. There is no dedicated &lt;code&gt;public&lt;/code&gt; folder for it; the &lt;code&gt;root&lt;/code&gt; ITSELF is its public folder.&lt;/li&gt;
&lt;li&gt;Symfony has to be physically separated from ASP but remain part of the same project. So, we tucked the Symfony source code into &lt;code&gt;/root/src&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Naturally, Symfony has its own &lt;code&gt;public&lt;/code&gt; directory in &lt;code&gt;/root/public&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;And to top it off, Symfony bundles reside nicely in &lt;code&gt;/root/bundles&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over time, we wanted to make development more efficient. First, we moved to the local Symfony CLI server, later we integrated Webpack Encore, and today – in 2026 – we are successfully migrating to Asset Mapper.&lt;/p&gt;

&lt;p&gt;Here is a guide on how we overcame three levels of this bizarre "boss fight".&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 1: Symfony Server vs. "Where is my root?!"
&lt;/h2&gt;

&lt;p&gt;While we used IIS in production and WAMP for local development, everything ran relatively smoothly. The problems started the moment we discovered the magic of the local Symfony CLI server. It desperately searched for the root folder wherever &lt;code&gt;app_dev.php&lt;/code&gt; was located.&lt;/p&gt;

&lt;p&gt;However, our entry point was hidden in &lt;code&gt;/root/public/app.php&lt;/code&gt;. The Symfony server stubbornly decided that our public folder would be &lt;code&gt;/root/public/app.php&lt;/code&gt;. Oops.&lt;/p&gt;

&lt;p&gt;How do you force the Symfony server to tolerate this structural anarchy? We looked for a solution for a long time until we arrived at an elegant "hack". As they say in the legacy world: "If it works, don't touch it!"&lt;/p&gt;

&lt;p&gt;We created a file &lt;code&gt;/root/app_dev.php&lt;/code&gt; directly in the root directory with this little gem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'REQUEST_URI'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// If the request is routed to our first attempt at migrating to PHP using a custom framework&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'~(/rdsfm/)(\?.*)?~'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$matches&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Location: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;index.php&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Otherwise, send it without mercy to the real Symfony&lt;/span&gt;
&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Location: /public/app_dev.php"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done. A simple routing on the level of a plain PHP file beautifully spun up a modern Symfony server within our ancient legacy project.&lt;/p&gt;




&lt;h2&gt;
  
  
  Level 2: Webpack Encore and Black Magic in ASP
&lt;/h2&gt;

&lt;p&gt;Introducing Webpack Encore for the Symfony part was a walk in the park. Hell broke loose when we needed to wire that same build into the old ASP code.&lt;/p&gt;

&lt;p&gt;Logically, Classic ASP doesn't have a native JSON parser. And why would it? Back when it was created, JSON was probably just a wild dream of a few visionaries. For the integration, we needed a script that would help create the entrypoint. AI had to lend a helping hand here, because a regular mortal would have lost their mind writing this code.&lt;/p&gt;

&lt;p&gt;We created a file &lt;code&gt;rds_js.asp&lt;/code&gt;, where we literally manually parse the JSON, extract the necessary section from it, and generate HTML tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;%
' **********************************************************************************************
' * THIS WAS CREATED BY AI. NOBODY KNOWS EXACTLY WHAT IT DOES OR HOW IT WORKS. IT'S A BLACK HOLE...*
' **********************************************************************************************
'
' REAL DESCRIPTION (for mortals):
' This script loads and manually parses the public/build/entrypoints.json file,
' because Classic ASP does not know JSON. It searches for the "old_rds" section, extracts the "js" array, 
' and generates the appropriate &amp;lt;script src="..." defer&amp;gt; tag for each found file.
'
dim fs_rds, f_rds, content_rds, entrypointsPath_rds
dim startOldRds, endOldRds, oldRdsSection
dim startJs, endJs, jsSection
dim jsFiles, jsFile, i

entrypointsPath_rds = Server.MapPath("/public/build/entrypoints.json")

set fs_rds = Server.CreateObject("Scripting.FileSystemObject")
' ... the rest of the code follows here, turning this dark magic into reality
%&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we didn't want to pull all the heavy dependencies from Symfony into ASP, we created a special entrypoint that contained only the absolute necessities:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;webpack.config.js&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Encore&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOutputPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public/build/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPublicPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/public/build&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./assets/app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEntry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;old_rds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./assets/old_rds.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Our legacy breadcrumb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Level 3: The Year 2026 and the Arrival of Asset Mapper
&lt;/h2&gt;

&lt;p&gt;The year is 2026. We are deployed exclusively on a local network, disconnected from the internet, and a few extremely complex ASP scripts still haunt our system. A complete rewrite of these monsters is still a long-term goal.&lt;/p&gt;

&lt;p&gt;In all our other (modern) projects, we're already happily using Asset Mapper. The only place where Node.js still annoys us is this legacy project. "Let's finally get rid of it!" I told myself one day, full of naive optimism.&lt;/p&gt;

&lt;p&gt;However, we once again ran into our old friend – the problem with the specific directory structure. On the local development environment, Asset Mapper worked perfectly because assets are not versioned there.&lt;/p&gt;

&lt;p&gt;The real problem was the production build. Suddenly, assets were built directly into &lt;code&gt;/root/assets&lt;/code&gt;, which was completely wrong for our structure. After long hours of exploring the guts of the Asset Mapper library and futile attempts at decorating services, I finally found an elegant and simple solution.&lt;/p&gt;

&lt;p&gt;All it took was to properly instruct the path resolver for production only:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;config/packages/prod/asset_mapper.yaml&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;when@prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;asset_mapper.public_assets_path_resolver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Symfony\Component\AssetMapper\Path\PublicAssetsPathResolver&lt;/span&gt;
            &lt;span class="na"&gt;arguments&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;$publicPrefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/assets_build/'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then clearly tell Composer where to install everything:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;composer.json&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"auto-scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"assets:install ./"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"symfony-cmd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"importmap:install"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"symfony-cmd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"asset-map:compile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"symfony-cmd"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"extra"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symfony-web-dir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"public-dir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symfony-assets-install"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"absolute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"installer-paths"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what about our good old ASP code? Since we ditched Node.js and Webpack Encore, our terrifying AI-generated script for parsing JSON (&lt;code&gt;rds_js.asp&lt;/code&gt;) was no longer needed! &lt;/p&gt;

&lt;p&gt;Instead, we decided to leverage Symfony itself. We created a simple Symfony Console command that runs automatically after &lt;code&gt;composer install&lt;/code&gt; (via the post-install scripts in &lt;code&gt;composer.json&lt;/code&gt;). This command uses the native &lt;code&gt;ImportMapRenderer&lt;/code&gt; to render the required HTML tags for the Asset Mapper and directly saves them into the &lt;code&gt;rds_js.asp&lt;/code&gt; file. &lt;/p&gt;

&lt;p&gt;Here is the code of this command:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;projects/rds/src/Command/GenerateRdsJsCommand.php&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;AsCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'app:generate-rds-js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Generates rds_js.asp using ImportMapRenderer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateRdsJsCommand&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//...&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;InputInterface&lt;/span&gt; &lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;OutputInterface&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;importMapRenderer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'old_rds'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="nv"&gt;$html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'"/assets/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'/assets/"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'"/public/assets/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'/public/assets/"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_replace&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'"/assets_build/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'/assets_build/"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'"/public/assets_build/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'/public/assets_build/"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$targetFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/rds/rds_js.asp'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_put_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$targetFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;writeln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;info&amp;gt;Successfully generated &lt;/span&gt;&lt;span class="nv"&gt;$targetFile&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/info&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;writeln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;error&amp;gt;Failed to write to &lt;/span&gt;&lt;span class="nv"&gt;$targetFile&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/error&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FAILURE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the old ASP codebase simply includes the generated &lt;code&gt;rds_js.asp&lt;/code&gt; file and happily consumes modern assets mapped directly by Symfony. No more manual JSON parsing!&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Working with a legacy system while trying to integrate modern technologies is not always a walk in the park. It requires a massive dose of patience, the courage to use non-standard "hacks", and occasionally the help of AI when you need to do something as absurd as parsing JSON in Classic ASP.&lt;/p&gt;

&lt;p&gt;But you know what? That feeling when you get rid of another outdated dependency and the whole creaky behemoth moves one step closer to a modern architecture—that is simply priceless.&lt;/p&gt;

&lt;p&gt;Do you have similar Frankenstein systems in your company? How do you fight them? Share your experiences in the comments!&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>php</category>
      <category>legacy</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Performance Testing PHP Applications: Load Testing with K6 and Artillery</title>
      <dc:creator>Patoliya Infotech</dc:creator>
      <pubDate>Fri, 15 May 2026 07:02:42 +0000</pubDate>
      <link>https://dev.to/patoliyainfotech/performance-testing-php-applications-load-testing-with-k6-and-artillery-3igj</link>
      <guid>https://dev.to/patoliyainfotech/performance-testing-php-applications-load-testing-with-k6-and-artillery-3igj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Load testing is not optional for production PHP applications. This guide covers two of the best modern tools, &lt;strong&gt;K6&lt;/strong&gt; and &lt;strong&gt;Artillery&lt;/strong&gt;, with real scripts, PHP-specific gotchas, CI/CD integration, and a framework for interpreting results so you know exactly when your app is ready (and when it isn't).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why PHP Apps Need Load Testing
&lt;/h2&gt;

&lt;p&gt;PHP powers a staggering portion of the web, from WordPress blogs to enterprise Laravel APIs to high-traffic e-commerce platforms. Yet performance testing is consistently one of the most skipped steps in PHP development workflows.&lt;/p&gt;

&lt;p&gt;The consequences are predictable: a product launch doubles traffic, the database connection pool saturates, opcache fills up, and suddenly a healthy-looking application is returning 502s.&lt;/p&gt;

&lt;p&gt;Load testing answers questions your unit tests and code reviews never can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;How many concurrent users can my app handle before response times degrade?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Where exactly does it break, PHP-FPM, MySQL, Redis, or the application code itself?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does my session handling degrade under concurrent writes?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does my Laravel queue back up under spike traffic?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're building &lt;a href="https://www.patoliyainfotech.com/technologies/php" rel="noopener noreferrer"&gt;custom PHP applications&lt;/a&gt; for production use, load testing isn't a one-time checkbox, it's a recurring discipline tied to every major feature release.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Performance Testing Concepts Refresher
&lt;/h2&gt;

&lt;p&gt;Before diving into tools, let's align on terminology:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load Test&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simulate expected traffic to measure normal behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stress Test&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Push beyond expected limits to find the breaking point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spike Test&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sudden burst of traffic, simulates a flash sale or viral event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Soak Test&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sustained load over hours, finds memory leaks and resource exhaustion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VU (Virtual User)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A simulated user executing your test script concurrently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RPS (Requests/sec)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Throughput, how many HTTP requests your app handles per second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;p95 / p99 latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;95th / 99th percentile response time, the tail that users actually feel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;% of requests that returned 4xx/5xx, your availability signal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The metric that matters most in practice:&lt;/strong&gt; &lt;code&gt;p99 latency&lt;/code&gt;. Your average response time can look great while 1% of users wait 8 seconds. In a Laravel API serving 10,000 RPM, that's 100 frustrated users every minute.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tool Overview: K6 vs Artillery
&lt;/h2&gt;

&lt;p&gt;Both tools are excellent. Here's how they differ:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;K6&lt;/th&gt;
&lt;th&gt;Artillery&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Script language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JavaScript (ES6+)&lt;/td&gt;
&lt;td&gt;YAML + optional JS processors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP, WebSocket, gRPC&lt;/td&gt;
&lt;td&gt;HTTP, WebSocket, Socket.io&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learning curve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low–Medium&lt;/td&gt;
&lt;td&gt;Very Low (YAML)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Real browser simulation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ (k6 Browser)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud execution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Grafana Cloud k6&lt;/td&gt;
&lt;td&gt;✅ Artillery Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CI/CD integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;td&gt;✅ Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output / metrics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Rich, Prometheus-compatible&lt;/td&gt;
&lt;td&gt;JSON, HTML reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API &amp;amp; complex flow testing&lt;/td&gt;
&lt;td&gt;Quick HTTP load tests, microservices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AGPL-3 (OSS) + commercial&lt;/td&gt;
&lt;td&gt;MPL-2 (OSS) + commercial&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reach for &lt;strong&gt;K6&lt;/strong&gt; when you need programmable, conditional logic, thresholds as code, and CI gates.&lt;/li&gt;
&lt;li&gt;Reach for &lt;strong&gt;Artillery&lt;/strong&gt; when you want YAML-driven speed, readable scenario configs, and simple team onboarding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many teams use both: Artillery for quick smoke tests, K6 for authoritative load runs in CI.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These tools integrate cleanly into &lt;a href="https://www.patoliyainfotech.com/services/devops-consulting" rel="noopener noreferrer"&gt;DevOps pipelines&lt;/a&gt;, making performance a first-class deployment gate rather than an afterthought.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setting Up Your Test Environment
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Critical rule: Never load test production.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Always test against a staging environment that mirrors production as closely as possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Same PHP version (match your production FPM config)
✅ Same web server (Nginx/Apache + PHP-FPM pool settings)
✅ Same database engine + approximate data volume
✅ Same caching layer (Redis / Memcached / OPcache enabled)
✅ Same CDN bypass (test origin, not CDN-cached responses)
✅ Network latency similar to real users (add artificial latency if testing locally)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PHP-FPM configuration to note before testing:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;; /etc/php/8.x/fpm/pool.d/www.conf
&lt;/span&gt;&lt;span class="py"&gt;pm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;dynamic&lt;/span&gt;
&lt;span class="py"&gt;pm.max_children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;50        ; This is your hard concurrency ceiling&lt;/span&gt;
&lt;span class="py"&gt;pm.start_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10&lt;/span&gt;
&lt;span class="py"&gt;pm.min_spare_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;pm.max_spare_servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;35&lt;/span&gt;
&lt;span class="py"&gt;pm.max_requests&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;500       ; Restart workers after N requests (memory leak mitigation)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your load test should push &lt;code&gt;pm.max_children&lt;/code&gt; to observe exactly what happens when the pool is exhausted, that's the most important number to know.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Configuring staging environments that accurately mirror production is a core part of robust &lt;a href="https://www.patoliyainfotech.com/services/software-testing-qa" rel="noopener noreferrer"&gt;software testing and QA&lt;/a&gt; practices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Load Testing with K6
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;k6

&lt;span class="c"&gt;# Ubuntu / Debian&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--no-default-keyring&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--keyring&lt;/span&gt; /usr/share/keyrings/k6-archive-keyring.gpg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--keyserver&lt;/span&gt; hkp://keyserver.ubuntu.com:80 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--recv-keys&lt;/span&gt; C5AD17C747E3415A3642D57D77C6C491D6AC1D69

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/k6.list

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;k6

&lt;span class="c"&gt;# Docker (no install required)&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; grafana/k6 run - &amp;lt;script.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Your First K6 Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// basic-load-test.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Test options, 10 VUs for 30 seconds&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;30s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://staging.yourphpapp.com/api/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Assertions, failures increment k6's error counter&lt;/span&gt;
  &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status is 200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;response time &amp;lt; 500ms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body contains data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"data"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Think time between requests, simulates real user behaviour&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;k6 run basic-load-test.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;✓ status is 200
✓ response time &amp;lt; 500ms
✓ body contains data

http_req_duration............: avg=143ms  min=89ms  med=131ms  max=487ms  p(90)=201ms  p(95)=289ms
http_req_failed..............: 0.00%  ✓ 0      ✗ 300
iterations...................: 300    10.0/s
vus..........................: 10     min=10   max=10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Virtual Users &amp;amp; Stages
&lt;/h3&gt;

&lt;p&gt;Real traffic doesn't spike instantly. Model realistic ramp-up patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// staged-load-test.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Ramp up to 20 VUs over 2 minutes&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Hold at 20 VUs for 5 minutes&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Ramp to 50 VUs - stress territory&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;// Hold at stress level&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;    &lt;span class="c1"&gt;// Ramp down - watch for recovery&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://staging.yourphpapp.com/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status 200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Random 1-4s think time&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The ramp-down phase is underrated.&lt;/strong&gt; Watch whether your app recovers cleanly as load drops. PHP-FPM workers that don't release database connections properly will hold locks even after VUs go to zero.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Thresholds &amp;amp; SLOs
&lt;/h3&gt;

&lt;p&gt;K6 thresholds turn your performance requirements into &lt;strong&gt;pass/fail CI gates&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 95% of requests must complete in under 400ms&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http_req_duration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

    &lt;span class="c1"&gt;// 99th percentile must stay under 1 second&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http_req_duration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(99)&amp;lt;1000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

    &lt;span class="c1"&gt;// Less than 1% of requests can fail&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http_req_failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rate&amp;lt;0.01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

    &lt;span class="c1"&gt;// Specific URL thresholds using tags&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http_req_duration{url:https://staging.yourphpapp.com/api/checkout}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;600&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any threshold is breached, &lt;code&gt;k6 run&lt;/code&gt; exits with a non-zero code - perfect for failing a CI build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing PHP Auth Flows
&lt;/h3&gt;

&lt;p&gt;Most PHP apps require authentication. Here's a realistic Laravel Sanctum / session-based login flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// auth-flow-test.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://staging.yourphpapp.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http_req_duration{group:::Login}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;800&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http_req_duration{group:::Dashboard}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;400&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http_req_failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rate&amp;lt;0.02&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Step 1: Get CSRF token (Laravel requires this)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csrfRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/sanctum/csrf-cookie`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csrfToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;csrfRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csrfRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 2: Authenticate&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/login`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`testuser&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;__VU&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@example.com`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// __VU = virtual user ID&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;csrfToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginRes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login successful&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;token returned&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dashRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/dashboard`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dashRes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard loads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data present&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing PHP APIs with Dynamic Data
&lt;/h3&gt;

&lt;p&gt;Using K6's SharedArray for realistic, parameterised test data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// parameterised-test.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SharedArray&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6/data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;k6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Loaded once, shared across all VUs (memory-efficient)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SharedArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./test-users.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Array of {email, password}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SharedArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./test-products.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Array of product IDs&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;vus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;thresholds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http_req_duration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p(95)&amp;lt;500&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http_req_failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rate&amp;lt;0.01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Pick a user based on VU index (ensures each VU uses a consistent user)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;__VU&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

  &lt;span class="c1"&gt;// Authenticate&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://staging.yourphpapp.com/api/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Browse product&lt;/span&gt;
  &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://staging.yourphpapp.com/api/products/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Add to cart&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cartRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://staging.yourphpapp.com/api/cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cartRes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;added to cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Load Testing with Artillery
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installation &amp;amp; YAML Config
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; artillery
artillery version  &lt;span class="c"&gt;# Verify install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A basic Artillery config for a PHP application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# basic-load-test.yml&lt;/span&gt;
&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://staging.yourphpapp.com"&lt;/span&gt;
  &lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;      &lt;span class="c1"&gt;# seconds&lt;/span&gt;
      &lt;span class="na"&gt;arrivalRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;    &lt;span class="c1"&gt;# new users per second&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Warm-up"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;
      &lt;span class="na"&gt;arrivalRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sustained&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Load"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
      &lt;span class="na"&gt;arrivalRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Peak&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Spike"&lt;/span&gt;
  &lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json"&lt;/span&gt;
      &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json"&lt;/span&gt;

&lt;span class="na"&gt;scenarios&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Browse&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;product&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;catalogue"&lt;/span&gt;
    &lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/products"&lt;/span&gt;
          &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;think&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/products/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;productId&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
          &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;artillery run basic-load-test.yml
&lt;span class="c"&gt;# With HTML report&lt;/span&gt;
artillery run &lt;span class="nt"&gt;--output&lt;/span&gt; results.json basic-load-test.yml
artillery report results.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scenarios &amp;amp; Phases
&lt;/h3&gt;

&lt;p&gt;Artillery's multi-scenario config is ideal for modelling realistic traffic mixes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# realistic-traffic-mix.yml&lt;/span&gt;
&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://staging.yourphpapp.com"&lt;/span&gt;
  &lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
      &lt;span class="na"&gt;arrivalRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Normal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;traffic"&lt;/span&gt;
  &lt;span class="na"&gt;processor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./processors.js"&lt;/span&gt;  &lt;span class="c1"&gt;# Custom JS logic&lt;/span&gt;

&lt;span class="na"&gt;scenarios&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# 60% of traffic: anonymous browsing&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Anonymous&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Browse"&lt;/span&gt;
    &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
    &lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/products?page={{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$randomInt(1,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;10)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;think&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/products/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$randomInt(1,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;500)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;think&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;

  &lt;span class="c1"&gt;# 30% of traffic: authenticated users&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authenticated&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;User&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Flow"&lt;/span&gt;
    &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
    &lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/login"&lt;/span&gt;
          &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
          &lt;span class="na"&gt;capture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$.token"&lt;/span&gt;
              &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authToken"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/account/orders"&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;authToken&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;think&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="c1"&gt;# 10% of traffic: checkout flow&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Checkout"&lt;/span&gt;
    &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/login"&lt;/span&gt;
          &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
          &lt;span class="na"&gt;capture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$.token"&lt;/span&gt;
              &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authToken"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/cart"&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;authToken&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
          &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;product_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$randomInt(1,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;100)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/checkout"&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;authToken&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
          &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;payment_method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;card"&lt;/span&gt;
          &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;201&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom Processors (Node.js)
&lt;/h3&gt;

&lt;p&gt;For dynamic data generation and complex logic, Artillery supports JS processor functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// processors.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@faker-js/faker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Called before each scenario&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateUserData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test_password_123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Custom response validator&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateProductResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid_product_response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateUserData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;validateProductResponse&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In your YAML config:&lt;/span&gt;
&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;processor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./processors.js"&lt;/span&gt;

&lt;span class="na"&gt;scenarios&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dynamic&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;User&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Test"&lt;/span&gt;
    &lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generateUserData"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/register"&lt;/span&gt;
          &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing Laravel / Symfony Routes
&lt;/h3&gt;

&lt;p&gt;A targeted test for a Laravel e-commerce API, covering the critical path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# laravel-ecommerce-test.yml&lt;/span&gt;
&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://staging.yourphpapp.com"&lt;/span&gt;
  &lt;span class="na"&gt;phases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
      &lt;span class="na"&gt;arrivalRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Warm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;up&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PHP-FPM&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pool"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
      &lt;span class="na"&gt;arrivalRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Production&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;simulation"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;
      &lt;span class="na"&gt;arrivalRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;75&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Black&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Friday&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;spike"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
      &lt;span class="na"&gt;arrivalRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Recovery&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;observation"&lt;/span&gt;
  &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;            &lt;span class="c1"&gt;# Fail requests &amp;gt; 10s&lt;/span&gt;
    &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;              &lt;span class="c1"&gt;# HTTP connection pool size&lt;/span&gt;
  &lt;span class="na"&gt;ensure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;p99&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2000&lt;/span&gt;              &lt;span class="c1"&gt;# Fail test if p99 &amp;gt; 2000ms&lt;/span&gt;
    &lt;span class="na"&gt;maxErrorRate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;        &lt;span class="c1"&gt;# Fail test if error rate &amp;gt; 1%&lt;/span&gt;

&lt;span class="na"&gt;scenarios&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Product&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Search&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;→&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PDP&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;→&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Cart&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;→&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Checkout"&lt;/span&gt;
    &lt;span class="na"&gt;flow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Search&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/search?q=laptop&amp;amp;per_page=20"&lt;/span&gt;
          &lt;span class="na"&gt;capture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$.data[0].id"&lt;/span&gt;
              &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;productId"&lt;/span&gt;

      &lt;span class="c1"&gt;# Product Detail Page&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/products/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;productId&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

      &lt;span class="c1"&gt;# Login&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/login"&lt;/span&gt;
          &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loadtest_{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$loopCount&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}@example.com"&lt;/span&gt;
            &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password"&lt;/span&gt;
          &lt;span class="na"&gt;capture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$.token"&lt;/span&gt;
              &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token"&lt;/span&gt;

      &lt;span class="c1"&gt;# Add to Cart&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/cart/items"&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
          &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;product_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;productId&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
          &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;201&lt;/span&gt;

      &lt;span class="c1"&gt;# Checkout&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/orders"&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
          &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;shipping_address_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
            &lt;span class="na"&gt;payment_nonce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fake-valid-nonce"&lt;/span&gt;
          &lt;span class="na"&gt;expect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;201&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  PHP-Specific Optimisation Targets
&lt;/h2&gt;

&lt;p&gt;When your load test reveals problems, here's where to look first in a PHP stack:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. PHP-FPM Pool Exhaustion
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Latency climbs sharply, then requests start failing with 502/504 as load increases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Monitor FPM pool status in real time during load test&lt;/span&gt;
watch &lt;span class="nt"&gt;-n&lt;/span&gt; 1 &lt;span class="s1"&gt;'curl -s http://localhost/fpm-status | grep -E "active|idle|max"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;; Increase max_children (but watch RAM, each PHP worker ~30–80MB)
&lt;/span&gt;&lt;span class="py"&gt;pm.max_children&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;100&lt;/span&gt;

&lt;span class="c"&gt;; Enable slow log to find expensive scripts
&lt;/span&gt;&lt;span class="py"&gt;slowlog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/log/php-fpm-slow.log&lt;/span&gt;
&lt;span class="py"&gt;request_slowlog_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. OPcache Misconfiguration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; First requests after deploy are slow; warm requests are fine. OPcache fill events show up in metrics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;; php.ini - OPcache tuning for production
&lt;/span&gt;&lt;span class="py"&gt;opcache.enable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;opcache.memory_consumption&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;256       ; MB - increase for large codebases&lt;/span&gt;
&lt;span class="py"&gt;opcache.max_accelerated_files&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;20000  ; Must exceed file count in your app&lt;/span&gt;
&lt;span class="py"&gt;opcache.validate_timestamps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0        ; Disable in production for max speed&lt;/span&gt;
&lt;span class="py"&gt;opcache.revalidate_freq&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="py"&gt;opcache.jit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1255                     ; Enable JIT (PHP 8.x)&lt;/span&gt;
&lt;span class="py"&gt;opcache.jit_buffer_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;128M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Database Connection Pool Saturation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Query times are fine in isolation but explode under concurrent load. &lt;code&gt;SHOW PROCESSLIST&lt;/code&gt; shows hundreds of connections in &lt;code&gt;Sleep&lt;/code&gt; state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/database.php (Laravel)&lt;/span&gt;
&lt;span class="s1"&gt;'mysql'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'driver'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'mysql'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'host'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_HOST'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// ... other config&lt;/span&gt;
    &lt;span class="s1"&gt;'options'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="no"&gt;PDO&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ATTR_PERSISTENT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Persistent connections - use with care&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'pool'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'min'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'max'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Match your pm.max_children&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better yet, use a connection pooler like &lt;strong&gt;PgBouncer&lt;/strong&gt; (PostgreSQL) or &lt;strong&gt;ProxySQL&lt;/strong&gt; (MySQL) to decouple PHP-FPM workers from DB connections.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. N+1 Queries Under Load
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Single-user response time is 80ms; 50-user response time is 4000ms. The scaling is non-linear.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ N+1 - 1 query for orders + N queries for each user&lt;/span&gt;
&lt;span class="nv"&gt;$orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orders&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$order&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Executes a query per iteration&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Eager loading - 2 queries total regardless of count&lt;/span&gt;
&lt;span class="nv"&gt;$orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;These are the kinds of scalability issues that come up repeatedly in &lt;a href="https://www.patoliyainfotech.com/technologies/php" rel="noopener noreferrer"&gt;PHP application development&lt;/a&gt;, especially in Laravel and CodeIgniter apps that don't enforce eager loading at the architectural level.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5. Session Handling Under Concurrency
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Logged-in users intermittently get logged out or see stale data under load.&lt;/p&gt;

&lt;p&gt;PHP's default file-based sessions create &lt;strong&gt;write locks&lt;/strong&gt; - one request blocks all others for the same session. At 50+ concurrent VUs sharing sessions, this serialises your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/session.php (Laravel) - Switch to Redis&lt;/span&gt;
&lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SESSION_DRIVER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'redis'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="c1"&gt;// .env&lt;/span&gt;
&lt;span class="no"&gt;SESSION_DRIVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;
&lt;span class="no"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;127.0.0.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Redis for session storage is also a stepping stone to &lt;a href="https://www.patoliyainfotech.com/services/database-cloud-transformation" rel="noopener noreferrer"&gt;database and cloud transformation&lt;/a&gt; - moving stateful components out of the server and into scalable, distributed infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Integrating Load Tests into CI/CD
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions with K6
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/load-test.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Load Test&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;load-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install K6&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;sudo gpg --no-default-keyring \&lt;/span&gt;
            &lt;span class="s"&gt;--keyring /usr/share/keyrings/k6-archive-keyring.gpg \&lt;/span&gt;
            &lt;span class="s"&gt;--keyserver hkp://keyserver.ubuntu.com:80 \&lt;/span&gt;
            &lt;span class="s"&gt;--recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69&lt;/span&gt;
          &lt;span class="s"&gt;echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \&lt;/span&gt;
            &lt;span class="s"&gt;| sudo tee /etc/apt/sources.list.d/k6.list&lt;/span&gt;
          &lt;span class="s"&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install k6&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Load Test&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k6 run --out json=results.json ./tests/load/smoke-test.js&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;BASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.STAGING_URL }}&lt;/span&gt;
        &lt;span class="c1"&gt;# k6 exits non-zero if thresholds are breached → build fails ✅&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Results&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;k6-results&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;results.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitHub Actions with Artillery
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/artillery-test.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Artillery Load Test&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;artillery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Artillery&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install -g artillery&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Smoke Test&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;artillery run \&lt;/span&gt;
            &lt;span class="s"&gt;--output artillery-results.json \&lt;/span&gt;
            &lt;span class="s"&gt;./tests/load/smoke-test.yml&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check Results&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# Extract p99 from results and fail if &amp;gt; 1000ms&lt;/span&gt;
          &lt;span class="s"&gt;P99=$(cat artillery-results.json | jq '.aggregate.latency.p99')&lt;/span&gt;
          &lt;span class="s"&gt;echo "p99 latency: ${P99}ms"&lt;/span&gt;
          &lt;span class="s"&gt;if (( $(echo "$P99 &amp;gt; 1000" | bc -l) )); then&lt;/span&gt;
            &lt;span class="s"&gt;echo "❌ p99 latency exceeded threshold"&lt;/span&gt;
            &lt;span class="s"&gt;exit 1&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate HTML Report&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;artillery report artillery-results.json&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Report&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;artillery-report&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;artillery-results.html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Embedding load tests as deployment gates is a hallmark of mature &lt;a href="https://www.patoliyainfotech.com/blog/ci-cd-pipeline-guide/" rel="noopener noreferrer"&gt;CI/CD pipeline&lt;/a&gt; practices - it transforms performance from a reactive investigation into a proactive quality standard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Reading &amp;amp; Interpreting Results
&lt;/h2&gt;

&lt;h3&gt;
  
  
  K6 Output Decoded
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;http_req_duration............: avg=234ms min=89ms med=201ms max=2341ms p(90)=389ms p(95)=512ms p(99)=1204ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;What It Tells You&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;avg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overall average - useful but misleading if skewed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;med&lt;/code&gt; (p50)&lt;/td&gt;
&lt;td&gt;The "typical" user experience&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;p90&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9 in 10 users see this or better&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;p95&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your SLO target - 95% of users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;p99&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your tail - 1% still experience this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single worst request - often an outlier but worth investigating&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Red flags in K6 output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;http_req_failed..............: 5.23% ✗ 156   ← &amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1% means something is broken
&lt;span class="go"&gt;http_req_duration............: p(95)=4200ms   ← Well above 500ms SLO
checks.....................: 78.45%            ← &amp;lt; 100% means assertions are failing
vus_max...................: 50                 ← Were all VUs actually running?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Artillery Report Metrics
&lt;/h3&gt;

&lt;p&gt;Artillery's HTML report gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency histogram&lt;/strong&gt; - Distribution of response times&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RPS over time&lt;/strong&gt; - Did throughput hold steady or degrade?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error count timeline&lt;/strong&gt; - When did errors start?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scenario completion rate&lt;/strong&gt; - Did multi-step flows complete?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The key question for every load test result:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Does the p99 latency grow linearly with load, or does it knee-bend exponentially at a specific VU count?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A linear growth (p99 200ms → 400ms as VUs double) suggests normal queuing. An exponential knee (p99 200ms → 3000ms when going from 40 to 50 VUs) pinpoints your exact saturation point - usually PHP-FPM pool exhaustion or a DB connection limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common PHP Performance Bottlenecks (and Fixes)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom in Load Test&lt;/th&gt;
&lt;th&gt;Root Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;502/504 at high VUs&lt;/td&gt;
&lt;td&gt;PHP-FPM pool exhausted&lt;/td&gt;
&lt;td&gt;Increase &lt;code&gt;pm.max_children&lt;/code&gt;, add horizontal scaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow first request after deploy&lt;/td&gt;
&lt;td&gt;OPcache cold start&lt;/td&gt;
&lt;td&gt;Pre-warm OPcache with &lt;code&gt;opcache_compile_file()&lt;/code&gt; in deploy script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-linear latency growth&lt;/td&gt;
&lt;td&gt;N+1 queries&lt;/td&gt;
&lt;td&gt;Eager load with &lt;code&gt;with()&lt;/code&gt; in Laravel / Doctrine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory growth over soak test&lt;/td&gt;
&lt;td&gt;Memory leak in long-lived code&lt;/td&gt;
&lt;td&gt;Lower &lt;code&gt;pm.max_requests&lt;/code&gt;, profile with Blackfire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session errors under concurrency&lt;/td&gt;
&lt;td&gt;File-based session locking&lt;/td&gt;
&lt;td&gt;Migrate to Redis sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sporadic 500s under load&lt;/td&gt;
&lt;td&gt;Unhandled exceptions in queue&lt;/td&gt;
&lt;td&gt;Add retry logic + dead-letter queues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow API under concurrency&lt;/td&gt;
&lt;td&gt;Missing DB indexes&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;EXPLAIN&lt;/code&gt; on slow queries, add composite indexes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High CPU, low latency&lt;/td&gt;
&lt;td&gt;Inefficient PHP code&lt;/td&gt;
&lt;td&gt;Profile with Xdebug / Blackfire, look at hot loops&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Systematically addressing these bottlenecks is part of what makes &lt;a href="https://www.patoliyainfotech.com/services/custom-software-development" rel="noopener noreferrer"&gt;custom software development&lt;/a&gt; built for scale different from code written just to pass functional tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion &amp;amp; Further Reading
&lt;/h2&gt;

&lt;p&gt;Load testing is the closest thing you have to a &lt;strong&gt;crystal ball for production&lt;/strong&gt;. The tools have never been better: K6 gives you the programmability of a full test framework, and Artillery gives you YAML-driven simplicity for fast iteration. Used together - and integrated into your CI/CD pipeline - they transform performance from a reactive emergency into a measurable, manageable quality attribute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick recap:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────┐
│  Goal                          │  Tool / Technique               │
├────────────────────────────────┼─────────────────────────────────┤
│  Quick smoke test              │  Artillery YAML                 │
│  Programmable load + gates     │  K6 with thresholds             │
│  Auth flows                    │  K6 groups + CSRF handling      │
│  Realistic traffic mix         │  Artillery weighted scenarios   │
│  CI/CD integration             │  GitHub Actions + exit codes    │
│  PHP-FPM tuning insight        │  fpm-status + slow log          │
│  DB bottleneck diagnosis       │  EXPLAIN + ProxySQL/PgBouncer   │
│  Memory leaks                  │  Soak test + Blackfire          │
└──────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The process in four steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define SLOs first&lt;/strong&gt; - p95 &amp;lt; 400ms, error rate &amp;lt; 1%. Without a target, any result is acceptable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run baseline&lt;/strong&gt; - What's the app like at 1 VU? That's your ceiling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ramp to break&lt;/strong&gt; - Increase VUs until thresholds are breached. Note the exact breaking point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix, re-test, document&lt;/strong&gt; - The test only has value if it drives a change and verifies it worked.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Further Reading &amp;amp; Resources
&lt;/h3&gt;

&lt;p&gt;Deepen your PHP performance knowledge with these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/technologies/php" rel="noopener noreferrer"&gt;PHP Development Guide&lt;/a&gt;&lt;/strong&gt; - Architecture considerations for production-grade PHP apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/services/software-testing-qa" rel="noopener noreferrer"&gt;Software Testing &amp;amp; QA Services&lt;/a&gt;&lt;/strong&gt; - Beyond load testing: how QA integrates performance into the release cycle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/services/devops-consulting" rel="noopener noreferrer"&gt;DevOps Consulting&lt;/a&gt;&lt;/strong&gt; - Infrastructure strategies that support horizontally scalable PHP deployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/blog/ci-cd-pipeline-guide/" rel="noopener noreferrer"&gt;CI/CD Pipeline Guide&lt;/a&gt;&lt;/strong&gt; - Automating quality gates including performance thresholds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/services/custom-software-development" rel="noopener noreferrer"&gt;Custom Software Development&lt;/a&gt;&lt;/strong&gt; - Building PHP systems with performance requirements baked in from day one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/services/database-cloud-transformation" rel="noopener noreferrer"&gt;Database &amp;amp; Cloud Transformation&lt;/a&gt;&lt;/strong&gt; - Scaling the data layer that load tests almost always expose first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/blog/web-application-development-services/" rel="noopener noreferrer"&gt;Web Application Development Services&lt;/a&gt;&lt;/strong&gt; - Full-stack context for where PHP performance fits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/blog/guide-query-optimization-in-database-systems/" rel="noopener noreferrer"&gt;Query Optimization in Database Systems&lt;/a&gt;&lt;/strong&gt; - Deep dive on the DB bottlenecks load tests surface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/blog/automation-testing-in-modern-development/" rel="noopener noreferrer"&gt;Automation Testing in Modern Development&lt;/a&gt;&lt;/strong&gt; - Fitting load tests into a broader automated quality strategy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/technologies/laravel" rel="noopener noreferrer"&gt;Laravel Technology&lt;/a&gt;&lt;/strong&gt; - Laravel-specific performance patterns and configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.patoliyainfotech.com/technologies/node" rel="noopener noreferrer"&gt;Node.js Development&lt;/a&gt;&lt;/strong&gt; - Artillery runs on Node.js; understanding the runtime helps with custom processors&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>performance</category>
      <category>testing</category>
    </item>
    <item>
      <title>Laracon EU 2026: What Amsterdam told us about the future of Laravel</title>
      <dc:creator>Dewald Hugo</dc:creator>
      <pubDate>Fri, 15 May 2026 06:22:54 +0000</pubDate>
      <link>https://dev.to/dewaldhugo/laracon-eu-2026-what-amsterdam-told-us-about-the-future-of-laravel-1b59</link>
      <guid>https://dev.to/dewaldhugo/laracon-eu-2026-what-amsterdam-told-us-about-the-future-of-laravel-1b59</guid>
      <description>&lt;p&gt;Laracon EU 2026 was the largest European Laravel conference on record. Roughly 1,000 developers descended on the Passenger Terminal Amsterdam on March 2 and 3, a glass-and-steel venue perched directly on the River IJ. What they witnessed across those two days was not a routine framework update. It was a restatement of intent.&lt;/p&gt;

&lt;p&gt;Taylor Otwell didn’t just announce Laravel 13 at Laracon EU 2026. He released a new vision: “The clean stack for Artisans and agents.” That framing matters. “Agents” is not marketing copy here. It is a concrete architectural direction. If you work on AI-adjacent Laravel applications (and increasingly, that means most of us) what happened in Amsterdam directly shapes the decisions you make this quarter. This piece covers every headline announcement, the talks worth watching, and the practical implications for production teams.&lt;/p&gt;

&lt;p&gt;This article is also part of our broader &lt;a href="https://origin-main.com/laravel-ai-architecture/" rel="noopener noreferrer"&gt;AI Architecture coverage&lt;/a&gt;. If you’re thinking through how AI fits your Laravel stack at a structural level, that is where to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Venue, the Scale, and the Energy
&lt;/h2&gt;

&lt;p&gt;The Passenger Terminal is not a conventional conference hall. It is a working cruise terminal with a main hall large enough to seat a thousand people without feeling cramped. The energy was palpable from the moment Nuno Maduro opened the conference. This year’s Laracon EU, with around 1,000 attendees, was the largest European Laravel conference ever.&lt;/p&gt;

&lt;p&gt;That attendance figure matters beyond the headline number. The community that showed up was not uniform. Teams from agencies, product companies, and freelancers. Developers who had never attended a Laracon before alongside people who have been at every edition. As always with Laracons, Laracon EU 2026 was more than a conference. It’s not just about the talks. It’s about spending time with like-minded people and sharing an experience as well as celebrating the Laravel community. The pre-party the evening before was where half the real conversations happened. The dinner conversations after Taylor’s keynote ran until midnight.&lt;/p&gt;

&lt;p&gt;The practical takeaway here: if you are weighing whether to attend in 2027, the value is not just the stage content. It is the corridor conversations with people debugging the same problems you are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taylor Otwell’s Keynote: The “Merge It” Moment
&lt;/h2&gt;

&lt;p&gt;By far the most discussed moment of the conference was Taylor Otwell’s keynote on the evening of the first day. It was scheduled to close Day 1, and by 9pm the main hall was still packed. What followed was genuinely hard to describe without sounding like you are exaggerating.&lt;/p&gt;

&lt;p&gt;If there was one single moment that made the vision behind the entire Laravel ecosystem tangible, it was Taylor’s live demo on stage: The complete Software Development Lifecycle, from error detection to deployment, in under two minutes, live in front of 1,000 attendees.&lt;/p&gt;

&lt;p&gt;The sequence was deliberate. A bug occurs in the application. Nightwatch detects the error automatically. An AI agent with Nightwatch MCP integration analyses and fixes the error autonomously. A pull request is automatically opened. Laravel Cloud creates a preview environment. Taylor reviews, says “Merge it”. The change goes live.&lt;/p&gt;

&lt;p&gt;Voice-driven. No manually written code. Two minutes.&lt;/p&gt;

&lt;p&gt;The jawdropping moment was when the AI agent called Taylor and told him “There’s a PR in the repo, do you want me to merge it?” And then it happened: bug fixed and auto-deployed, all with just voice, without writing any code.&lt;/p&gt;

&lt;p&gt;The room reaction was not the usual polite applause of a conference keynote. It was the particular silence that precedes a standing ovation, because people needed a few seconds to process what they had just seen. We’ve all talked about AI-assisted development in the abstract. This was a functioning, live demonstration of the entire loop closing: monitoring, diagnosis, remediation, review, and deployment, as a single integrated workflow.&lt;/p&gt;

&lt;p&gt;Taylor himself put it succinctly: Laravel, with its clear conventions, comprehensive documentation, and 15 years of example code, is ideally positioned for an era in which AI agents are increasingly becoming part of the development process.&lt;/p&gt;

&lt;p&gt;That is not a boast. It is a defensible structural argument. The reason LLM agents work well with Laravel is precisely because the framework has consistent, well-documented conventions that agents can reason about reliably. Convention over configuration turns out to be excellent training data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laravel 13: What Actually Shipped
&lt;/h2&gt;

&lt;p&gt;Laravel 13 was released on March 17, 2026, announced by Taylor Otwell at Laracon EU 2026, with zero breaking changes, making it the smoothest upgrade in Laravel’s history. The only requirement change is PHP 8.3 minimum.&lt;/p&gt;

&lt;p&gt;The headline features are worth taking individually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PHP Attributes across 15+ locations.&lt;/strong&gt; Laravel 13 will include PHP attributes across 15+ locations. These are optional, so nothing breaks if you don’t adopt them immediately. Middleware registration, authorisation policies, and job configuration can all now be expressed declaratively on the class itself rather than in separate registration files. Whether you adopt them on Day 1 or wait for the ecosystem to settle is a judgement call, but the direction is clear: Laravel is moving toward the attribute syntax as its idiomatic configuration style. If you are still registering middleware in long arrays, your code is already going to read as dated within a release cycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reverb database driver.&lt;/strong&gt; A Reverb database driver arrives with Laravel 13, eliminating the need for Redis in smaller broadcasting setups. This is a significant quality-of-life improvement for teams that run Reverb but do not need Redis for anything else. It reduces infrastructure surface area. Whether it holds up under real concurrency pressure is something we will find out over the next few months as teams hit it in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Passkey authentication.&lt;/strong&gt; Passkey authentication is coming to all starter kits and Laravel Fortify, complemented by a client-side companion package. This is the right move, and it is overdue. Password-based authentication has a long tail of support burden attached to it. Passkeys remove that tail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache::touch().&lt;/strong&gt; A small addition, but one that solves a genuine annoyance: extending a cache entry’s TTL without re-fetching or re-computing the value. We have implemented this manually more than once using custom cache wrappers. Having it built in is welcome.&lt;/p&gt;

&lt;p&gt;For the complete feature analysis from an AI development perspective, see &lt;a href="https://origin-main.com/laravel/laravel-13-ai-sdk-features-breakdown/" rel="noopener noreferrer"&gt;what Laravel 13 actually changes for AI development&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Laravel AI SDK Goes Stable
&lt;/h2&gt;

&lt;p&gt;This is the announcement with the longest tail. On the same date as Laravel 13’s release, the Laravel AI SDK was tagged as officially stable and out of beta.&lt;/p&gt;

&lt;p&gt;The SDK ships with several capabilities that matter in production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider-agnostic design.&lt;/strong&gt; The SDK abstracts the interface, allowing you to switch between different LLM providers without rewriting application logic. &lt;strong&gt;Model failover:&lt;/strong&gt; if one model goes down, a fallback automatically kicks in, critical for production applications. &lt;strong&gt;Agent Classes with schema-defined output:&lt;/strong&gt; structured AI responses that integrate in a type-safe manner into existing application logic. &lt;strong&gt;Vector embedding and similarity search:&lt;/strong&gt; integrated directly into Eloquent, significantly simplifying semantic search in Laravel applications. &lt;strong&gt;Queuing of AI tasks:&lt;/strong&gt; AI tasks can be processed through Laravel’s queue system as usual.&lt;/p&gt;

&lt;p&gt;The provider-agnostic design is the decision that matters most here. Any team currently hard-coding an OpenAI or Anthropic SDK directly into their service layer is building technical debt. The Laravel AI SDK gives you the abstraction layer that most teams were building manually anyway. Our &lt;a href="https://origin-main.com/laravel-architecture/laravel-ai-integration-architecture-guide/" rel="noopener noreferrer"&gt;production guide to provider-agnostic AI integration in Laravel&lt;/a&gt; covers the architectural patterns that the SDK now formalises at the framework level. If you are migrating existing AI integration code, that is the reference.&lt;/p&gt;

&lt;p&gt;[Architect’s Note] The SDK’s Eloquent-integrated vector search is the quiet headline most teams will underestimate. Semantic search without a separate vector database layer is not a complete RAG replacement for every use case, but for the majority of content-search scenarios most Laravel apps actually need, it removes an entire infrastructure dependency. Assess whether you still need pgvector or Pinecone before reaching for them by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  NativePHP Super Native: Blade Goes Truly Native
&lt;/h2&gt;

&lt;p&gt;If one talk generated the most feverish corridor conversations after it ended, it was the NativePHP thread across both conference days.&lt;/p&gt;

&lt;p&gt;Simon Hamp presented the current state of NativePHP for Mobile: the modular plugin architecture, the free open-source core “NativePHP Air,” and the tooling around Jump and Bifrost for App Store deployments. That was already interesting. Then Shane Rosenthal took the stage on Day 2 and went further.&lt;/p&gt;

&lt;p&gt;“Super Native” is an entirely new approach where Blade components and Tailwind CSS classes are translated directly into native UI elements through a custom parser. No WebView, no bridge, no overhead. The benchmarks showed UI interactions in the microsecond range, in some tests faster than React Native and Flutter. The live demo showed a functional to-do app with Eloquent, various native UI components, and app clones generated by an LLM in a single pass that rendered as 100% native UI.&lt;/p&gt;

&lt;p&gt;We were sceptical on first pass. Then the demo ran. The implications are real: PHP teams could build native mobile and desktop applications without leaving the Laravel ecosystem. Whether Super Native holds up under complex UI requirements in production apps is an open question. The honest answer is that nobody knows yet, because the benchmarks shown were controlled environments. But the proof of concept is convincing enough that it warrants serious evaluation for internal tools and MVPs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Talks That Will Shape Your Next Sprint
&lt;/h2&gt;

&lt;p&gt;Beyond the keynote, Laracon EU 2026 had one of the strongest community talk lineups in recent memory. A few that are worth watching back:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Yannick Kupferschmidt: AI Won’t Fail Loudly, It’ll Fail Quietly.&lt;/strong&gt; The danger of AI agents doesn’t lie in obvious errors, but in subtle bugs and gradual architecture drift. The antidote is “Context Engineering”: not just better prompts, but structured guidelines files, reusable skills, and deliberate control over what an agent receives as context. Larger context windows don’t automatically mean better results. Targeted context does. This is the talk most teams building AI features need to watch first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Peter Suhm: Unblocking Your Users with AI.&lt;/strong&gt; A pragmatic framework for integrating AI into existing Laravel applications rather than greenfield projects. Three practical entry points depending on where users get stuck, with real-world examples. No vague “AI-first” positioning. Concrete integration points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marcel Pociot: Refactoring to Parallel.&lt;/strong&gt; Marcel’s talk on asynchronous refactoring patterns paired well with the AI SDK’s queue integration story. If you are building AI pipelines and not yet thinking about concurrency, this one is relevant immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tobias Petry: One Billion Records with Laravel.&lt;/strong&gt; One of the more technically dense talks of the conference. Query optimisation, indexing strategy, and Eloquent behaviour at scale. If your application is approaching data volumes where default Eloquent patterns start to break, this is required watching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inertia v3.&lt;/strong&gt; The beta for Inertia v3 was due to be tagged at any time following the conference. Simpler, slimmer, and with reduced client-side overhead. If your team runs Inertia-based frontends, the upgrade path is worth evaluating now rather than waiting for it to become urgent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dan Harrin: Abstractions through description, not instruction.&lt;/strong&gt; One of the underrated talks of the event. The core argument: better abstractions emerge from describing the problem well, not from instructing your way to a solution. Directly applicable to AI prompt design and to API contract design in equal measure.&lt;/p&gt;

&lt;p&gt;For teams looking to &lt;a href="https://origin-main.com/ai-agents/laravel-prism-php-agentic-apps/" rel="noopener noreferrer"&gt;build agentic Laravel applications&lt;/a&gt; that hold up in production, Kupferschmidt’s talk on quiet AI failure is the one to pair with your architecture decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI as Infrastructure, Not a Feature
&lt;/h2&gt;

&lt;p&gt;The most important thing about Laracon EU 2026 was not any single announcement. It was the tone.&lt;/p&gt;

&lt;p&gt;AI was not presented as an add-on or a differentiator you bolt onto an existing application. It was treated as infrastructure, the same category as queues, caching, and broadcasting. The Laravel AI SDK ships with queue integration. Vector search ships inside Eloquent. Model failover ships as a first-class SDK concern.&lt;/p&gt;

&lt;p&gt;That framing carries a practical implication: teams that are building AI features as isolated packages or service classes bolted onto otherwise conventional Laravel applications are going to find themselves refactoring again in twelve months. The framework is standardising around conventions for AI integration. Teams that adopt those conventions early will carry less technical debt.&lt;/p&gt;

&lt;p&gt;If you are currently evaluating &lt;a href="https://origin-main.com/stack/laravel-ai-development-stack-setup/" rel="noopener noreferrer"&gt;the current AI development stack for Laravel&lt;/a&gt;, the SDK stable release changes several of the decisions that were previously in play.&lt;/p&gt;

&lt;p&gt;The production deployment implications deserve equal attention. The “Merge It” demo was not just about AI. It was about what a closed-loop deployment pipeline looks like when every component in the ecosystem integrates at the API level: Nightwatch, Cloud, the AI agent, the queue system. If you are still running manual deployment workflows, the &lt;a href="https://origin-main.com/guides/deploy-laravel-to-production/" rel="noopener noreferrer"&gt;production deployment guide&lt;/a&gt; is the right place to close that gap before the tooling gets further ahead of your process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Verdict
&lt;/h2&gt;

&lt;p&gt;Laracon EU 2026 was not a conference about what might be possible. What unfolded in real time wasn’t some future scenario. It’s the concrete direction the Laravel ecosystem is heading: A fully integrated workflow where monitoring, AI-powered debugging, code review, and deployment seamlessly interlock.&lt;/p&gt;

&lt;p&gt;The announcements are real. The SDK is stable. Laravel 13 is live. The “Merge It” demo ran without edits in front of a thousand people.&lt;/p&gt;

&lt;p&gt;What Laracon EU 2026 made clear is that the question for production Laravel teams is no longer “should we integrate AI?” The question is “how close to the framework’s conventions are we building when we do it?” The teams that answer that question well in the next six months will be significantly further ahead by the time Laracon EU 2027 arrives.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>eu</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
