<?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: Raheel Shan</title>
    <description>The latest articles on DEV Community by Raheel Shan (@raheelshan).</description>
    <link>https://dev.to/raheelshan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F605559%2F6c4cc957-0c22-48fe-a1de-aa790e2cd394.jpg</url>
      <title>DEV Community: Raheel Shan</title>
      <link>https://dev.to/raheelshan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/raheelshan"/>
    <language>en</language>
    <item>
      <title>The Repository Pattern and AQC — Part 1 of 3: Cleaning Up the Internals</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Fri, 29 May 2026 09:32:49 +0000</pubDate>
      <link>https://dev.to/raheelshan/the-repository-pattern-and-aqc-part-1-of-3-cleaning-up-the-internals-3h6l</link>
      <guid>https://dev.to/raheelshan/the-repository-pattern-and-aqc-part-1-of-3-cleaning-up-the-internals-3h6l</guid>
      <description>&lt;h2&gt;
  
  
  The Series
&lt;/h2&gt;

&lt;p&gt;This is the first article in a three-part series on migrating from a traditional Repository Pattern implementation toward Atomic Query Construction. Each article represents a stage in that migration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 1 (this article):&lt;/strong&gt; AQC is adopted internally. The repository interface stays the same. Each method privately builds its own &lt;code&gt;$params&lt;/code&gt; and delegates to AQC. Callers notice nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; Parameter definition moves out of the repository and up to the service layer. The repository shrinks to a pure wrapper and its redundancy becomes visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; Two paths forward — drop the repository entirely and call AQC directly, or keep the repository and discipline it with AQC practices without using AQC classes at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are working with an existing codebase and a team that depends on a familiar repository interface, Part 1 is your entry point. It is the lowest-risk way to introduce AQC, and it delivers immediate benefits even before you take the pattern further.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem This Stage Solves
&lt;/h2&gt;

&lt;p&gt;A traditional Repository Pattern implementation for a product domain might look like this:&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="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ProductRepositoryInterface&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;getAllProducts&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;getProductById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&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;getActiveProducts&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;createProduct&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;$productData&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;updateProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&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;$productData&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;deleteProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&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;getProductsByCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$categoryId&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;updateStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$quantity&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;Eight methods. Each has its own implementation. And inside those implementations, query logic is written directly — Eloquent calls, &lt;code&gt;where&lt;/code&gt; clauses, &lt;code&gt;orderBy&lt;/code&gt;, eager loading — repeated and scattered across methods. When &lt;code&gt;getActiveProducts&lt;/code&gt; and &lt;code&gt;getProductsByCategory&lt;/code&gt; both need to eager-load &lt;code&gt;category&lt;/code&gt; and &lt;code&gt;inventory&lt;/code&gt;, that eager-load is written twice. When the ordering changes, two methods need updating. When a new condition is added to "active" products, every method that deals with active products needs to be found and updated.&lt;/p&gt;

&lt;p&gt;This is the internal problem. The interface bloat — too many methods — is a separate concern, and we will address it in Part 2. The problem this article solves is the &lt;strong&gt;query duplication and scattered logic inside the existing methods&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AQC eliminates this without touching the repository's public interface at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Approach
&lt;/h2&gt;

&lt;p&gt;Each repository method remains publicly named and individually responsible for its domain operation. But instead of writing Eloquent query logic directly, each method privately constructs a &lt;code&gt;$params&lt;/code&gt; array that describes what it needs, and delegates the actual query construction and execution to a dedicated AQC class.&lt;/p&gt;

&lt;p&gt;The repository method owns two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deciding what parameters apply&lt;/strong&gt; to this specific operation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calling the AQC class&lt;/strong&gt; with those parameters&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The AQC class owns one thing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Building and executing the query&lt;/strong&gt; from whatever parameters it receives&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The caller owns nothing new. The interface is unchanged.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — The AQC Classes
&lt;/h2&gt;

&lt;p&gt;We create one AQC class per CRUD operation. Each class contains all possible conditions for that operation. Repository methods activate the conditions they need by including the relevant keys in &lt;code&gt;$params&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GetProducts:&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\AQC\Product&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;GetProducts&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;array&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Collection&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nc"&gt;LengthAwarePaginator&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_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;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'min_stock'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stock_quantity'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'min_stock'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'search'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;$q&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'like'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'search'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&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="nf"&gt;orWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sku'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'like'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'search'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'with'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'with'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'order_by'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'order_by'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'order_dir'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'desc'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'desc'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'per_page'&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="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'per_page'&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="nv"&gt;$query&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;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;&lt;strong&gt;GetProduct:&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\AQC\Product&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;GetProduct&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;array&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?Product&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&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="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&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;$params&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="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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sku'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sku'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sku'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'with'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'with'&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="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&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;&lt;strong&gt;StoreProduct:&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\AQC\Product&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;StoreProduct&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;array&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Product&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;Product&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;'name'&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'description'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'price'&lt;/span&gt;          &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'sku'&lt;/span&gt;            &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sku'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;'stock_quantity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'stock_quantity'&lt;/span&gt;&lt;span class="p"&gt;]&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="s1"&gt;'category_id'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&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="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'active'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;UpdateProduct:&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\AQC\Product&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;UpdateProduct&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;array&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&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="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&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;$params&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="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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_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;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'data'&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="o"&gt;&amp;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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;DeleteProduct:&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\AQC\Product&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;DeleteProduct&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;array&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&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="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&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;$params&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="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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'status'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'status'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_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;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All five classes are defined once. Every condition lives in exactly one place. No duplication anywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — The Repository Uses AQC Internally
&lt;/h2&gt;

&lt;p&gt;Now the repository is rewritten to use these AQC classes internally. The public interface is &lt;strong&gt;identical&lt;/strong&gt; to the original. Callers — controllers, services, tests — do not change a single line:&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;use&lt;/span&gt; &lt;span class="nc"&gt;App\AQC\Product\GetProducts&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;App\AQC\Product\GetProduct&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;App\AQC\Product\StoreProduct&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;App\AQC\Product\UpdateProduct&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;App\AQC\Product\DeleteProduct&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;ProductRepository&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ProductRepositoryInterface&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;getAllProducts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Collection&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$aqc&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;GetProducts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$aqc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'with'&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;'category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'inventory'&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getProductById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?Product&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$aqc&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;GetProduct&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$aqc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'id'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'with'&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;'category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'inventory'&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getActiveProducts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Collection&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$aqc&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;GetProducts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$aqc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&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;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'min_stock'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'with'&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;'category'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'inventory'&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;createProduct&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;$productData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Product&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$aqc&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;StoreProduct&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$aqc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$productData&lt;/span&gt;&lt;span class="p"&gt;);&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;updateProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$productId&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;$productData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$aqc&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;UpdateProduct&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$aqc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'id'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$productData&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;deleteProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$aqc&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;DeleteProduct&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$aqc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$productId&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getProductsByCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$categoryId&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Collection&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$aqc&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;GetProducts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$aqc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'category_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$categoryId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'with'&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;'inventory'&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;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;updateStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$quantity&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$aqc&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;UpdateProduct&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$aqc&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'id'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'data'&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;'stock_quantity'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$quantity&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Study what happened to each method. &lt;code&gt;getAllProducts&lt;/code&gt; no longer writes an Eloquent query — it builds a &lt;code&gt;$params&lt;/code&gt; array and delegates. &lt;code&gt;getActiveProducts&lt;/code&gt; does the same, with the conditions for "active" expressed as parameters. &lt;code&gt;updateStock&lt;/code&gt;, which once had its own direct Eloquent logic, now delegates to &lt;code&gt;UpdateProduct&lt;/code&gt; with a targeted &lt;code&gt;$params&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;Every method is now three to six lines: construct &lt;code&gt;$params&lt;/code&gt;, instantiate the AQC class, return the result. No raw Eloquent. No scattered &lt;code&gt;where&lt;/code&gt; clauses. No duplication.&lt;/p&gt;

&lt;p&gt;And crucially — &lt;strong&gt;the controller that calls &lt;code&gt;$repository-&amp;gt;getActiveProducts()&lt;/code&gt; has not changed&lt;/strong&gt;. The service that calls &lt;code&gt;$repository-&amp;gt;updateStock($id, $quantity)&lt;/code&gt; has not changed. The interface is identical. The migration is invisible to callers.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changed and What Did Not
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What changed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query logic is no longer scattered across repository methods&lt;/li&gt;
&lt;li&gt;Conditions like &lt;code&gt;'status' =&amp;gt; 'active'&lt;/code&gt; or eager-loading &lt;code&gt;['category', 'inventory']&lt;/code&gt; are defined once in &lt;code&gt;$params&lt;/code&gt; and handled once in the AQC class&lt;/li&gt;
&lt;li&gt;Adding a new condition to "active products" means changing one line in &lt;code&gt;GetProducts::handle()&lt;/code&gt;, not hunting down every method that touches active products&lt;/li&gt;
&lt;li&gt;The AQC classes are independently testable — you can verify query behavior by passing &lt;code&gt;$params&lt;/code&gt; combinations directly, without invoking the repository at all&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What did not change:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The repository interface — same method names, same signatures, same return types&lt;/li&gt;
&lt;li&gt;The number of repository methods — still eight&lt;/li&gt;
&lt;li&gt;The caller's experience — controllers and services call the same methods they always called&lt;/li&gt;
&lt;li&gt;The fact that new domain requirements still produce new repository methods&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Honest Limitation of This Stage
&lt;/h2&gt;

&lt;p&gt;This approach delivers real value, but it leaves one problem untouched: &lt;strong&gt;the repository still grows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a new business requirement arrives — say, &lt;code&gt;getLowStockProductsByCategory&lt;/code&gt; — a new method still gets added to the repository. The interface expands. The interface contract grows. And every new method follows the same pattern: build &lt;code&gt;$params&lt;/code&gt;, call AQC, return result. The methods become predictable and clean, but they keep accumulating.&lt;/p&gt;

&lt;p&gt;There is also a subtler issue. The repository is now making business decisions. &lt;code&gt;getActiveProducts&lt;/code&gt; knows that "active" means &lt;code&gt;status = active&lt;/code&gt; and &lt;code&gt;stock_quantity &amp;gt; 0&lt;/code&gt;. That business knowledge lives in the repository, which is a data access layer. Should it? If the definition of "active" changes — if it now also requires &lt;code&gt;verified = true&lt;/code&gt; — the change happens in the repository, not in the service or domain layer where business logic is supposed to live.&lt;/p&gt;

&lt;p&gt;These are the tensions that Part 2 resolves. By moving parameter definition out of the repository and up to the service layer, the repository loses its business knowledge entirely. It becomes a pure router. And once it is a pure router, its redundancy becomes impossible to ignore.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Stop at This Stage
&lt;/h2&gt;

&lt;p&gt;Not every codebase needs to go further. This stage is the right stopping point when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your team is not ready for the full AQC migration and needs a familiar repository interface&lt;/li&gt;
&lt;li&gt;You are refactoring a large legacy codebase incrementally and cannot change callers yet&lt;/li&gt;
&lt;li&gt;Your application is stable with a fixed set of domain operations that are unlikely to grow significantly&lt;/li&gt;
&lt;li&gt;The primary goal was eliminating internal query duplication, not restructuring the architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those describe your situation, this stage is a complete and valid destination. The internals are clean, the query logic is centralized, and the codebase is significantly more maintainable than before.&lt;/p&gt;

&lt;p&gt;If you want to go further — if you want to see the repository reduced to a wrapper and ultimately made optional — that is what Part 2 is for.&lt;/p&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After (Part 1)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository interface&lt;/td&gt;
&lt;td&gt;8 named methods&lt;/td&gt;
&lt;td&gt;8 named methods (unchanged)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query logic location&lt;/td&gt;
&lt;td&gt;Scattered in each method&lt;/td&gt;
&lt;td&gt;Centralized in AQC classes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caller impact&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplication&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Eliminated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business logic in repository&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (unchanged)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repository growth&lt;/td&gt;
&lt;td&gt;Unbounded&lt;/td&gt;
&lt;td&gt;Still unbounded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AQC adoption level&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Internal only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;This is Part 1 of a 3-part series. &lt;br&gt;
Part 2: "The Repository as a Wrapper — When AQC Makes the Repository Redundant." &lt;br&gt;
Part 3: "Two Paths — Go Direct with AQC, or Discipline Your Repository with AQC Practices."&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Raheel Shan is the originator of the Atomic Query Construction (AQC) design pattern. He writes about Laravel architecture, query design, and application structure at raheelshan.com, dev.to, and medium.com.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>designpatterns</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Inspector.dev (Neuron), Laravel AI SDK, and Prism PHP: A Practical Comparison for Laravel Developers</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Wed, 20 May 2026 19:23:14 +0000</pubDate>
      <link>https://dev.to/raheelshan/inspectordev-neuron-laravel-ai-sdk-and-prism-php-a-practical-comparison-for-laravel-1gn2</link>
      <guid>https://dev.to/raheelshan/inspectordev-neuron-laravel-ai-sdk-and-prism-php-a-practical-comparison-for-laravel-1gn2</guid>
      <description>&lt;p&gt;The PHP and Laravel ecosystem has had a quiet revolution in the past year. Where once you had to piece together raw HTTP calls to OpenAI's API and write your own retry logic, token counting, and provider-switching abstractions, you now have not one, not two, but three serious contenders for AI integration in your Laravel apps.&lt;/p&gt;

&lt;p&gt;I've spent the last several months evaluating all three in real projects — and I want to give you the honest, side-by-side comparison I wish I'd had when I started.&lt;/p&gt;

&lt;p&gt;The three tools in question are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inspector.dev / Neuron AI&lt;/strong&gt; — a PHP-agnostic agentic framework backed by the Inspector monitoring platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laravel AI SDK&lt;/strong&gt; (&lt;code&gt;laravel/ai&lt;/code&gt;) — Taylor Otwell's official, first-party SDK that shipped production-stable with Laravel 13&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prism PHP&lt;/strong&gt; (&lt;code&gt;echolabsdev/prism&lt;/code&gt;) — the community-built Laravel package by EchoLabs that's been quietly filling this gap since before the first party entered the room&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's dig in.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Lay of the Land
&lt;/h2&gt;

&lt;p&gt;Before I compare them, it's worth being clear about &lt;em&gt;what each one is trying to do&lt;/em&gt;, because they don't all occupy the same niche — and that's the most important thing to understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prism PHP&lt;/strong&gt; is a unified LLM integration layer for Laravel. You pick a provider (OpenAI, Anthropic, Gemini, Ollama, Mistral), fire off a fluent query, get a structured response. Think of it as a clean, opinionated HTTP abstraction over multiple AI providers. It's not an agent framework. It's not a monitoring tool. It's a very good way to talk to LLMs from Laravel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel AI SDK&lt;/strong&gt; is Laravel's official answer to that same problem, but with significantly broader scope. It handles text generation, image generation, audio transcription, embeddings, vector stores, structured output, RAG, and first-class Agent classes — all through a single, framework-native package. It shipped in beta with Laravel 12 and became production-stable in Laravel 13 (March 2026).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inspector.dev / Neuron AI&lt;/strong&gt; is a different animal entirely. Neuron is an agentic PHP framework — PHP-agnostic, not just Laravel-native — built by the team at Inspector.dev. Its ambition is to bring LangChain/LlamaIndex-style agentic orchestration to the PHP ecosystem. It comes with built-in observability through Inspector, which is its killer feature. You can monitor every LLM call, tool invocation, and agent step in real time.&lt;/p&gt;

&lt;p&gt;With that framing in mind, let's compare across dimensions that actually matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Provider Support
&lt;/h2&gt;

&lt;p&gt;All three tools take a multi-provider stance, but they differ in breadth and maturity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prism PHP&lt;/strong&gt; supports OpenAI, Anthropic, Gemini, Ollama, Mistral, and Groq. The interface is consistent across all of them — you switch providers by changing a single enum value. No other code changes required. This has been battle-tested in production by the community for over a year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel AI SDK&lt;/strong&gt; supports OpenAI, Anthropic, Gemini, Groq, xAI, and ElevenLabs out of the box, with more planned. The configuration lives in &lt;code&gt;config/ai.php&lt;/code&gt; and aligns with Laravel's familiar patterns. Taylor's team has also built smart fallbacks — if a provider hits a rate limit or goes down, the SDK can automatically fail over to another. That's genuinely impressive and not something you get out of the box with the other two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neuron AI&lt;/strong&gt; is similarly provider-agnostic. It uses a common &lt;code&gt;AIProviderInterface&lt;/code&gt; for all LLM calls, so switching from Anthropic to OpenAI to Gemini is a one-line change. The framework-agnostic design means the provider layer works the same whether you're on Laravel, Symfony, WordPress, or vanilla PHP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Laravel AI SDK&lt;/strong&gt; for the automatic failover feature. &lt;strong&gt;Runner-up: Prism&lt;/strong&gt;, which has the most mature multi-provider support in the wild.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Developer Experience
&lt;/h2&gt;

&lt;p&gt;This is where they diverge most sharply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prism PHP&lt;/strong&gt; has the best fluent API for raw LLM calls. Here's what a basic Anthropic call looks like:&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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Prism\Prism\Facades\Prism&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;Prism\Prism\Enums\Provider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Prism&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;using&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'claude-3-7-sonnet-latest'&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;withSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'prompts.system'&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;withPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Explain the Atomic Query Construction pattern.'&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;asText&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;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's clean. It's chainable. It feels Laravel-native even though it predates the official SDK. Testing utilities, response faking, and assertion helpers are included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel AI SDK&lt;/strong&gt; introduces the concept of &lt;code&gt;Agent&lt;/code&gt; classes — dedicated PHP classes that encapsulate your prompts, instructions, tools, and conversation history:&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="n"&gt;php&lt;/span&gt; &lt;span class="n"&gt;artisan&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="nc"&gt;SupportAgent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SupportAgent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;
&lt;span class="p"&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;instructions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'You are a helpful customer support agent...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&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;tools&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;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FetchOrderTool&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RefundTool&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="c1"&gt;// Usage:&lt;/span&gt;
&lt;span class="nv"&gt;$agent&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;SupportAgent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$agent&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Where is my order?'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a more structured, testable pattern than raw LLM calls. It aligns with how Laravel organizes everything else — you get an Artisan command, a clear class contract, and built-in conversation storage backed by database migrations the SDK publishes for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neuron AI&lt;/strong&gt; takes the Agent pattern the furthest. You extend a base &lt;code&gt;Agent&lt;/code&gt; class, define your provider, instructions, and tools, and get a full agentic system including RAG, multi-agent orchestration, workflow graphs with human-in-the-loop checkpoints, and streaming — all in PHP:&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="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;NeuronAI\Agent\Agent&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;NeuronAI\Agent\SystemPrompt&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;NeuronAI\Providers\Anthropic\Anthropic&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;CustomerServiceAgent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;
&lt;span class="p"&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;provider&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;AIProviderInterface&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.anthropic.key'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'claude-4-5-sonnet'&lt;/span&gt;&lt;span class="p"&gt;);&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;instructions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'You are a helpful customer service assistant'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Verify order details before processing refunds'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Always confirm actions with the customer'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Neuron approach is the most capable, but also has the steepest learning curve. It's clearly inspired by LangChain and LlamaIndex — those familiar with Python-side agentic development will feel at home.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Laravel AI SDK&lt;/strong&gt; for the best balance of structure and familiarity. &lt;strong&gt;Prism&lt;/strong&gt; wins for simple LLM integration with minimal boilerplate. &lt;strong&gt;Neuron&lt;/strong&gt; wins for complex agentic workloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Agentic Capabilities
&lt;/h2&gt;

&lt;p&gt;If you're building simple chat features or AI-powered text generation, all three will serve you well. But if you're building autonomous agents, multi-step workflows, or RAG systems, the picture changes quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prism PHP&lt;/strong&gt; is honest about what it is: a text/LLM integration layer. It's not trying to be an agent framework. It handles tool calling with a clean API, supports structured output, and manages multi-turn conversations — but orchestrating multi-agent workflows is outside its design scope.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel AI SDK&lt;/strong&gt; is building toward agents. The Agent class system, structured output, RAG support through vector stores, and tool calling are all first-class. The SDK also ships with built-in conversation storage (the &lt;code&gt;agent_conversations&lt;/code&gt; and &lt;code&gt;agent_conversation_messages&lt;/code&gt; tables), so maintaining chat context across sessions requires zero custom infrastructure. This is a huge win for typical SaaS applications. However, the SDK is still young, and the more advanced agentic patterns (workflow graphs, human-in-the-loop, event-driven multi-agent coordination) aren't yet in the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neuron AI&lt;/strong&gt; is the most complete agentic framework in the PHP ecosystem, full stop. It gives you workflow orchestration with event-driven architecture, streaming, human-in-the-loop checkpoints, agent-to-agent communication, vision capabilities, and a composable toolkit system. Neuron V2 rewrote the workflow system from scratch around event-driven patterns, making complex multi-step agent workflows practical and testable in PHP — something that was previously only achievable in Python-land.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Neuron AI&lt;/strong&gt; for serious agentic applications. &lt;strong&gt;Laravel AI SDK&lt;/strong&gt; for teams that need a structured starting point with room to grow.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Observability and Debugging
&lt;/h2&gt;

&lt;p&gt;This is where Inspector.dev / Neuron has a decisive, built-in advantage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prism PHP&lt;/strong&gt; has no built-in observability. You're relying on whatever logging and monitoring you've wired up yourself — Laravel Telescope, your own query observers, or an external APM. For simple use cases this is fine. For complex pipelines, it's a gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel AI SDK&lt;/strong&gt; fires events that you can listen to, which gives you hooks for custom logging and monitoring. But there's no first-party dashboard or trace timeline out of the box. You can build it, but you have to build it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neuron AI + Inspector.dev&lt;/strong&gt; is in a different league here. Because Neuron was built by the Inspector team, observability is first-class — not bolted on. Setting one environment variable is all it takes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSPECTOR_INGESTION_KEY=your_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, every agent execution — every LLM call, every tool invocation, every workflow step — shows up as a trace in the Inspector dashboard. You can see execution timelines, identify which step in a complex workflow failed, measure token usage per step, and debug non-deterministic agent behaviour in a way that is simply not possible with logging alone.&lt;/p&gt;

&lt;p&gt;For production AI agents, this observability is not a nice-to-have. It's essential. Agentic applications are probabilistic — same input does not always equal same output. Without a trace-level view of execution, debugging becomes a nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Neuron AI / Inspector.dev&lt;/strong&gt;, by a wide margin, for production agentic workloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Ecosystem Fit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prism PHP&lt;/strong&gt; is Laravel-only, but it's deeply Laravel-native. It uses the Laravel HTTP client internally, integrates with Laravel's service container, and follows all of Laravel's conventions. If you're a Laravel shop that doesn't care about PHP framework portability, Prism's Laravel-native ergonomics are hard to beat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel AI SDK&lt;/strong&gt; is also Laravel-only, but it's &lt;em&gt;first-party&lt;/em&gt; Laravel — which means long-term support guarantees, documentation that lives at laravel.com, and tight integration with future Laravel features. For teams that want to bet on the framework's official direction, this is the safest choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neuron AI&lt;/strong&gt; is deliberately framework-agnostic. Whether you're on Laravel, Symfony, WordPress, or vanilla PHP, Neuron integrates without friction. This is a conscious design decision to grow the PHP ecosystem broadly rather than fragment it by framework. For shops running mixed codebases — say, a Laravel SaaS and a Symfony backend — Neuron lets you share agent implementations across both. This cross-framework portability is genuinely rare in the PHP ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Laravel AI SDK&lt;/strong&gt; for pure Laravel shops. &lt;strong&gt;Neuron AI&lt;/strong&gt; for teams with mixed PHP codebases or long-term portability concerns.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Scope and Multimodality
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prism PHP&lt;/strong&gt;: Text generation, embeddings, image input (multi-modal), tool calling, structured output. Clean and focused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel AI SDK&lt;/strong&gt;: Text generation, image generation (DALL-E, Gemini), audio transcription (Whisper), embeddings, vector stores, RAG, file search, web search, reranking. Possibly the broadest scope of the three by design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neuron AI&lt;/strong&gt;: Text generation, tool calling, RAG, vector store integration, vision/image input, multi-agent orchestration, workflow graphs. Focused on the agentic surface area rather than generative media.&lt;/p&gt;

&lt;p&gt;If you need to generate images or transcribe audio directly through the same package, the Laravel AI SDK is the only one that covers you today without additional packages.&lt;/p&gt;




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

&lt;p&gt;After working with all three in real projects, here's how I think about choosing between them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Prism PHP if&lt;/strong&gt; you want to add LLM-powered features (text generation, tool calling, structured output) to a Laravel app as quickly as possible with minimal architecture overhead. It's the most mature community solution, the most ergonomic for straightforward use cases, and it's not going anywhere. If all you need is a clean way to talk to multiple LLM providers from Laravel, Prism is the right tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Laravel AI SDK if&lt;/strong&gt; you're starting a new Laravel 12/13 project and want to bet on the official, first-party direction. The Agent class model, database-backed conversation storage, built-in fallbacks, and official documentation support make this the safest long-term choice for teams building within the Laravel ecosystem. It's young, but it's Taylor Otwell's team maintaining it — that carries weight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Neuron AI if&lt;/strong&gt; you're building serious agentic applications — autonomous workflows, multi-agent systems, RAG pipelines that need to run reliably in production. The built-in observability through Inspector.dev is genuinely irreplaceable for this class of problem. And if your team works across multiple PHP frameworks, Neuron's framework-agnostic design is a strategic advantage no other tool offers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;What strikes me most about this moment is that the PHP ecosystem finally has credible answers to Python's LangChain and JavaScript's Vercel AI SDK. A year ago, if a CTO asked their PHP team to build autonomous agents, the honest answer was "we'd need Python for that." That's no longer true.&lt;/p&gt;

&lt;p&gt;Each of these tools reflects a different philosophy: Prism values ergonomics and focus. The Laravel AI SDK values official integration and comprehensive scope. Neuron values deep production reliability and cross-framework reach.&lt;/p&gt;

&lt;p&gt;None of them are wrong. They're solving different problems at different levels of abstraction. The best choice depends on your project's complexity, your team's PHP ecosystem footprint, and whether observability in production is a day-one requirement.&lt;/p&gt;

&lt;p&gt;For what it's worth: in my own projects at Ohad Technologies, I've been reaching for Prism for straightforward LLM integration and Neuron for anything involving multi-step agentic workflows where I need to understand what's happening in production. The Laravel AI SDK is the one I'm watching most closely — first-party support in a fast-moving space like AI is not a minor thing.&lt;/p&gt;

&lt;p&gt;Pick the right tool for the job. And if you're not sure, start with Prism — you can always layer in more architecture later.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Raheel Shan is the co-founder and CTO of Ohad Technologies and the originator of the Atomic Query Construction (AQC) design pattern. He writes about Laravel architecture, AI integration, and software engineering at &lt;a href="https://raheelshan.com" rel="noopener noreferrer"&gt;raheelshan.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>laravel</category>
      <category>php</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Your Laravel Queries Are Lying to You</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Fri, 27 Mar 2026 14:14:38 +0000</pubDate>
      <link>https://dev.to/raheelshan/your-laravel-queries-are-lying-to-you-4mp7</link>
      <guid>https://dev.to/raheelshan/your-laravel-queries-are-lying-to-you-4mp7</guid>
      <description>&lt;p&gt;How many times does &lt;code&gt;Product::latest()&lt;/code&gt; appear in your codebase?&lt;/p&gt;

&lt;p&gt;If the answer is more than once, you're not DRY — you're just organized duplication. Every repeated query entry point is another interpretation of how that query should behave. Inconsistencies creep in silently, and future changes become a scavenger hunt.&lt;/p&gt;

&lt;p&gt;Atomic Query Construction (AQC) fixes this with one rule: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every model gets exactly one query entry point, inside a dedicated execution class.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Every. Single. Time.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetProducts&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;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filters, sorting, pagination — all controlled through params. One place to change, one place to debug, zero drift.&lt;/p&gt;

&lt;p&gt;📖 Read the complete article &lt;a href="https://raheelshan.com/posts/atomic-query-construction-design-pattern-eliminating-repeated-query-snippets-for-good" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>cleancode</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Stop Writing Queries Everywhere: The Atomic Habit Your Laravel Codebase Needs</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Mon, 23 Mar 2026 07:19:24 +0000</pubDate>
      <link>https://dev.to/raheelshan/stop-writing-queries-everywhere-the-atomic-habit-your-laravel-codebase-needs-4g58</link>
      <guid>https://dev.to/raheelshan/stop-writing-queries-everywhere-the-atomic-habit-your-laravel-codebase-needs-4g58</guid>
      <description>&lt;h2&gt;
  
  
  You Have a Query Problem. You Just Haven’t Admitted It Yet.
&lt;/h2&gt;

&lt;p&gt;Let’s describe a normal Laravel codebase.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A controller fetches active users&lt;/li&gt;
&lt;li&gt;  A job fetches almost the same users, with one extra condition&lt;/li&gt;
&lt;li&gt;  A service fetches them again, missing a condition&lt;/li&gt;
&lt;li&gt;  A test rebuilds the query from scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same intent. Different queries. Different results.&lt;/p&gt;

&lt;p&gt;Nobody notices until something breaks. Then everyone starts guessing which version is “correct”.&lt;/p&gt;

&lt;p&gt;That’s the problem.&lt;/p&gt;

&lt;p&gt;Not complexity. Not scale.&lt;/p&gt;

&lt;p&gt;Inconsistency.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Isn’t Duplication. It’s Drift.
&lt;/h2&gt;

&lt;p&gt;People love saying “don’t repeat yourself”.&lt;/p&gt;

&lt;p&gt;They still write this everywhere:&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="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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;p&gt;Then somewhere else:&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="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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;whereNotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email_verified_at'&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;p&gt;Then somewhere else:&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="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'role'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'admin'&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;p&gt;Now you don’t have duplication.&lt;/p&gt;

&lt;p&gt;You have different definitions of the same thing.&lt;/p&gt;

&lt;p&gt;That’s worse.&lt;/p&gt;

&lt;p&gt;Because now your system behaves differently depending on which file you opened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rule: One Layer Owns All Queries
&lt;/h2&gt;

&lt;p&gt;There is only one way to stop this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every database query must live in one layer. That layer is AQC.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And once it exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Controllers do not write queries&lt;/li&gt;
&lt;li&gt;  Services do not write queries&lt;/li&gt;
&lt;li&gt;  Jobs do not write queries&lt;/li&gt;
&lt;li&gt;  Tests do not write queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a query exists outside AQC, it’s wrong. No debate. Its violation of AQC.&lt;/p&gt;

&lt;p&gt;Instead of this in a controller:&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;index&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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;whereNotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email_verified_at'&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users.index'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&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;You do this:&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;index&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetUsers&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;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'active'&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users.index'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'users'&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 query has one home.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same Rule Everywhere
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Controller
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetUsers&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;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'role'&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="n"&gt;role&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;
  
  
  Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetUsers&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;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'digest_enabled'&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="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Job
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetUsers&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;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'active'&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetUsers&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;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'active'&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="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nobody writes queries anymore.&lt;/p&gt;

&lt;p&gt;They just use them.&lt;/p&gt;

&lt;p&gt;And guess what? You have composition.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With AQC, you can make a single query class handle multiple contexts, without repeating yourself or risking divergence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Look at this example:&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\AQC\User&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;App\Models\User&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;GetUsers&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;array&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// apply filters conditionally&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'active'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'digest_enabled'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'digest_enabled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'digest_enabled'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'role'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'role'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'role'&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 sorting when requested otherwise do it on id by default&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sortBy'&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="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;])){&lt;/span&gt;
            &lt;span class="nv"&gt;$sortBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sortBy'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nv"&gt;$type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;    
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sortBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$type&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="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'paginate'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PAGINATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$query&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;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;Here’s what’s happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Conditional filters – Active users, digest-enabled users, role-based filtering – all controlled by the caller. Nothing is hard-coded in multiple places.&lt;/li&gt;
&lt;li&gt; Flexible sorting – The query supports dynamic ordering, but defaults to a predictable behavior if nothing is specified.&lt;/li&gt;
&lt;li&gt; Pagination or full collection – One class handles both paginated lists for the UI and full collections for jobs or exports.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is composition in action. One query class serves multiple consumers, each passing their parameters. The base conditions and logic live in one place. No controller, service, or job reimplements a filter or accidentally forgets a condition.&lt;/p&gt;

&lt;p&gt;The beauty? Every consumer gets exactly what it needs without duplicating or diverging query logic. You maintain a single source of truth for fetching users.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Gain (And What You Stop Losing)
&lt;/h2&gt;

&lt;p&gt;When queries live in one place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You stop redefining business rules&lt;/li&gt;
&lt;li&gt;  You stop forgetting conditions&lt;/li&gt;
&lt;li&gt;  You stop chasing bugs across files&lt;/li&gt;
&lt;li&gt;  You stop guessing which query is correct&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Change happens once. Behavior updates everywhere.&lt;/p&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Counts as a Violation
&lt;/h2&gt;

&lt;p&gt;Let’s make this uncomfortable:&lt;/p&gt;

&lt;p&gt;If you write this anywhere outside AQC:&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="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You just broke the architecture. You just violated the AQC Design pattern rule.&lt;/p&gt;

&lt;p&gt;Doesn’t matter if it’s “just one condition” Doesn’t matter if it’s “temporary” Doesn’t matter if it’s “faster this way”&lt;/p&gt;

&lt;p&gt;It’s wrong.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AQC only works if it’s enforced. Not suggested. But enforced.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Habit
&lt;/h2&gt;

&lt;p&gt;Before writing any query, there are only two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  It already exists → use it&lt;/li&gt;
&lt;li&gt;  It doesn’t exist → create it in AQC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no third option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Atomic Query Construction&lt;/strong&gt; removes the repeatitive queries by giving every query a single home. One layer. One class per operation. One public method. One parameter signature. Every consumer uses the same query, every time.&lt;/p&gt;

&lt;p&gt;Controllers call AQC classes. Services call AQC classes. Jobs call AQC classes. Tests call AQC classes. The database answers to one layer — and one layer only.&lt;/p&gt;

&lt;p&gt;Stop guessing. Stop duplicating. Stop chasing subtle divergences across files. Build the layer, enforce the rules, and make query discipline a habit.&lt;/p&gt;

&lt;p&gt;This is not optional. This is the foundation of a maintainable, predictable Laravel codebase.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>laravel</category>
      <category>aqcdesignpattern</category>
    </item>
    <item>
      <title>The Reasoning Behind AQC Class Design Choices</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Tue, 17 Mar 2026 18:51:43 +0000</pubDate>
      <link>https://dev.to/raheelshan/the-reasoning-behind-aqc-class-design-choices-1o8k</link>
      <guid>https://dev.to/raheelshan/the-reasoning-behind-aqc-class-design-choices-1o8k</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%2F9arnbdx4a882g8265ggd.jpg" 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%2F9arnbdx4a882g8265ggd.jpg" alt="The Reasoning Behind AQC Class Design Choices"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Atomic Query Construction (AQC) design pattern is all about precision, clarity, and predictability. Over time, I’ve developed a consistent structure for AQC classes that may seem restrictive at first glance, but each restriction serves a practical purpose. Let’s break down why AQC classes are defined the way they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. No Constructor: Avoiding Dependency Injection
&lt;/h2&gt;

&lt;p&gt;In many design patterns, constructors are used to inject dependencies—services, repositories, or other objects. AQC classes intentionally avoid constructors. Why? Because the AQC philosophy assumes that the underlying service or query builder never changes internally.&lt;/p&gt;

&lt;p&gt;Injecting a dependency through a constructor implies that the class can be swapped or configured differently for each instance. For AQC classes, this is unnecessary overhead. The logic is atomic, deterministic, and isolated. By not requiring a constructor, you eliminate boilerplate, reduce configuration errors, and keep the class focused purely on its query logic.&lt;/p&gt;

&lt;p&gt;It also simplifies instantiation. You just create an instance whenever needed, without worrying about passing services or setup parameters that will never vary.&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;$getUsers&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;GetUsers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// simple and predictable&lt;/span&gt;
&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$getUsers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;handle&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;'active'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Non-Static Methods: Encouraging Instance-Based Clarity
&lt;/h3&gt;

&lt;p&gt;Some developers might wonder: why not make &lt;code&gt;handle()&lt;/code&gt; static? A static method would avoid instantiation and look simpler. But static methods carry hidden costs: they obscure state, make testing harder, and encourage global coupling.&lt;/p&gt;

&lt;p&gt;Using non-static methods ensures each instance is independent, even if it doesn’t hold state now. It’s a design discipline. By always creating an object before calling &lt;code&gt;handle()&lt;/code&gt;, the code communicates intent: “this is a self-contained, reusable atomic query unit.”&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Single Public Method (handle) with params Array
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;handle()&lt;/code&gt; method is the only public interface of an AQC class. This makes the API extremely predictable: no guessing which method to call or which method affects the query.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;params&lt;/code&gt; array standardizes input. Whether the query needs a filter, a sort order, or pagination, everything is passed through a single array. This avoids proliferating method signatures and keeps the class atomic.&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;$params&lt;/span&gt; &lt;span class="o"&gt;=&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;'active'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'role'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'sort'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'created_at'&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetUsers&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;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Private methods handle the internal steps, such as building joins, filtering conditions, or selecting columns. They are invisible externally, which keeps the class contract simple and ensures maintainers only focus on the &lt;code&gt;handle&lt;/code&gt; method.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Why This Pattern Works
&lt;/h3&gt;

&lt;p&gt;This design achieves three key goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;Predictability&lt;/code&gt;: Every AQC class looks and behaves the same way.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Simplicity&lt;/code&gt;: No unnecessary constructors, no dependency injection, no multiple public methods.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Flexibility&lt;/code&gt;: While the class is simple externally, private methods allow complex query construction internally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By following these rules, teams can read, use, and maintain AQC classes without worrying about hidden state, dependencies, or inconsistent public APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;The structure of AQC classes—no constructor, non-static methods, single &lt;code&gt;handle()&lt;/code&gt; public method accepting a &lt;code&gt;**params**&lt;/code&gt; array—is deliberate. It enforces atomicity, simplicity, and predictability, which are the core principles of the Atomic Query Construction design pattern. Each class becomes a self-contained unit that can be easily reused, tested, and maintained without overengineering or unnecessary complexity.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>laravel</category>
      <category>aqcdesignpattern</category>
    </item>
    <item>
      <title>Why Atomic Query Construction (AQC) Intentionally Uses Arrays Instead of DTOs</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Mon, 16 Mar 2026 21:30:13 +0000</pubDate>
      <link>https://dev.to/raheelshan/why-atomic-query-construction-aqc-intentionally-uses-arrays-instead-of-dtos-3jfa</link>
      <guid>https://dev.to/raheelshan/why-atomic-query-construction-aqc-intentionally-uses-arrays-instead-of-dtos-3jfa</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%2Fn8g5sb81ugcsk2nvfo4k.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%2Fn8g5sb81ugcsk2nvfo4k.png" alt="Why Atomic Query Construction (AQC) Intentionally Uses Arrays Instead of DTOs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One question keeps appearing whenever someone looks at the Atomic Query Construction (AQC) pattern for the first time:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why are parameters passed as arrays instead of DTOs?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first glance, DTOs sound like the “cleaner” solution. They provide type safety, structure, and explicit contracts. In many architectures, DTOs make perfect sense.&lt;/p&gt;

&lt;p&gt;But AQC is intentionally designed around arrays, and replacing them with DTOs would undermine one of the core ideas of the pattern.&lt;/p&gt;

&lt;p&gt;To understand why, we need to look at how AQC actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  AQC Is Parameter-Driven
&lt;/h2&gt;

&lt;p&gt;The central idea behind AQC is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Parameters shape the query.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each parameter activates a specific atomic piece of logic.&lt;/p&gt;

&lt;p&gt;For example:&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;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'category_id'&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;'min_price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'max_price'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'with'&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;'brand'&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;Inside the query class:&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;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_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;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'min_price'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'min_price'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'max_price'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'max_price'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'with'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'with'&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;Each parameter independently contributes to the final query.&lt;/p&gt;

&lt;p&gt;This means query composition is dynamic.&lt;/p&gt;

&lt;p&gt;And this is where arrays become important.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cartesian Flexibility of Parameters
&lt;/h2&gt;

&lt;p&gt;AQC relies on the idea that parameters can combine freely.&lt;/p&gt;

&lt;p&gt;If you have five optional parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category_id
brand_id
price_min
price_max
with
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't just have five scenarios.&lt;/p&gt;

&lt;p&gt;You have dozens of possible combinations.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category_id
category_id + brand_id
category_id + price_min
brand_id + price_max
price_min + price_max
category_id + brand_id + with
brand_id + price_min + price_max
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number of combinations grows quickly.&lt;/p&gt;

&lt;p&gt;Arrays allow this naturally because parameters are simply present or absent.&lt;/p&gt;

&lt;p&gt;Each parameter acts like a switch that activates a piece of query logic.&lt;/p&gt;

&lt;p&gt;DTOs don't work well in this environment because they encourage rigid structures, where every field is predefined and expected.&lt;/p&gt;

&lt;p&gt;AQC is not designed for rigid structures.&lt;/p&gt;

&lt;p&gt;It is designed for combinatorial flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  DTOs Introduce Unnecessary Rigidity
&lt;/h2&gt;

&lt;p&gt;A DTO typically looks like this:&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductQueryDTO&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;?int&lt;/span&gt; &lt;span class="nv"&gt;$categoryId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;?int&lt;/span&gt; &lt;span class="nv"&gt;$brandId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;?int&lt;/span&gt; &lt;span class="nv"&gt;$priceMin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;?int&lt;/span&gt; &lt;span class="nv"&gt;$priceMax&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;This immediately creates a few problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. DTOs Define Structure Too Early
&lt;/h3&gt;

&lt;p&gt;DTOs force you to define all possible parameters upfront.&lt;/p&gt;

&lt;p&gt;But queries evolve.&lt;/p&gt;

&lt;p&gt;Tomorrow you may add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;min_stock
max_stock
rating
visibility
published_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your DTO grows. Then another developer creates another DTO for a slightly different query.&lt;/p&gt;

&lt;p&gt;Soon you have DTOs multiplying.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. DTOs Add an Extra Layer
&lt;/h3&gt;

&lt;p&gt;AQC classes already serve a single purpose: build and execute queries.&lt;/p&gt;

&lt;p&gt;Introducing DTOs means adding a translation layer:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Request → DTO → AQC → Query&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;With arrays the flow stays simple:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Request → AQC → Query&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;AQC intentionally avoids unnecessary layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. DTOs Reduce Dynamic Composition
&lt;/h3&gt;

&lt;p&gt;DTOs imply a fixed contract.&lt;/p&gt;

&lt;p&gt;But AQC is built on conditional query pieces.&lt;/p&gt;

&lt;p&gt;Each parameter activates a piece of logic.&lt;/p&gt;

&lt;p&gt;Arrays support that naturally because they allow parameters to appear or disappear without forcing structural constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arrays Match the Philosophy of AQC
&lt;/h2&gt;

&lt;p&gt;AQC is built on a few clear ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  One class = one intention&lt;/li&gt;
&lt;li&gt;  Parameters control query behavior&lt;/li&gt;
&lt;li&gt;  Queries are composed conditionally&lt;/li&gt;
&lt;li&gt;  Controllers remain thin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Arrays align perfectly with this philosophy.&lt;/p&gt;

&lt;p&gt;Each parameter acts as a trigger for an atomic query segment.&lt;/p&gt;

&lt;p&gt;That makes the query builder inside the AQC class extremely flexible without introducing complexity elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;DTOs are useful in many architectural patterns.&lt;/p&gt;

&lt;p&gt;But AQC is intentionally parameter-driven, and arrays preserve the dynamic nature of query composition.&lt;/p&gt;

&lt;p&gt;Each parameter activates an atomic query piece, and arrays allow those pieces to combine freely into a Cartesian number of possible query scenarios.&lt;/p&gt;

&lt;p&gt;Trying to force DTOs into this pattern doesn't improve it.&lt;/p&gt;

&lt;p&gt;It just makes the system heavier while removing the flexibility AQC was designed to provide.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>aqcdesignpattern</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Atomic Query Construction (AQC) Design Pattern: A Practical CRUD Implementation Guide</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Mon, 16 Mar 2026 20:32:16 +0000</pubDate>
      <link>https://dev.to/raheelshan/atomic-query-construction-aqc-design-pattern-a-practical-crud-implementation-guide-38hd</link>
      <guid>https://dev.to/raheelshan/atomic-query-construction-aqc-design-pattern-a-practical-crud-implementation-guide-38hd</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%2Fgo5bkmjlimh083lz8g51.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%2Fgo5bkmjlimh083lz8g51.png" alt="Atomic Query Construction (AQC) Design Pattern: A Practical CRUD Implementation Guide"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I first introduced the &lt;strong&gt;Atomic Query Construction (AQC)&lt;/strong&gt; design pattern, the focus was on breaking down monolithic repositories into small, single-purpose query classes. These classes are driven entirely by parameters, not hard-coded logic. That philosophy allows a single class to handle multiple query intentions without method explosion, while keeping code readable and maintainable.&lt;/p&gt;

&lt;p&gt;In this article, I want to show you how to implement AQC for full CRUD operations, following the same parameter-first approach. This is the practical way to apply the pattern in a Laravel application but you are free to adopt this pattern in any language of your preference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Philosophy Recap
&lt;/h2&gt;

&lt;p&gt;AQC is based on one principle&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One class = one intention. Parameters define the variations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Folder Structure
&lt;/h2&gt;

&lt;p&gt;A simple organization for CRUD operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
 └─ AQC/
     ├─ Product/
     │   ├─ GetProducts.php
     │   ├─ GetProduct.php
     │   ├─ CreateProduct.php
     │   ├─ UpdateProduct.php
     │   └─ DeleteProduct.php
     │
     └─ User/
         ├─ GetUsers.php
         ├─ GetUser.php
         ├─ CreateUser.php
         ├─ UpdateUser.php
         └─ DeleteUser.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. Fetch Multiple Records (GetProducts)  
&lt;/h2&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\AQC\Product&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;App\Models\Product&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;GetProducts&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="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$productObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;latest&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="c1"&gt;// apply filters&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;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="nv"&gt;$productObj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'brand_id'&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'brand_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;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="nv"&gt;$productObj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'brand_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'brand_id'&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 more filters according to your requirements&lt;/span&gt;

        &lt;span class="c1"&gt;// select columns&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'columns'&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="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'columns'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;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;$productObj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'columns'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$productObj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&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;// Apply sorting when requested otherwise do it on id by default&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sortBy'&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="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;])){&lt;/span&gt;
            &lt;span class="nv"&gt;$sortBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sortBy'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nv"&gt;$type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;    
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sortBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$type&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="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'paginate'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$productObj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PAGINATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$productObj&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;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 handle method grows create internal helper methods.&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\AQC\Product&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;App\Models\Product&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;GetProducts&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="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;latest&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;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;applyFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&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;selectColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&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;applySorting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'paginate'&lt;/span&gt;&lt;span class="p"&gt;])&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;handlePagination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$query&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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;applyFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;selectColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;applySorting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handlePagination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&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;h2&gt;
  
  
  2. Fetch a Single Record (GetProduct)  
&lt;/h2&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\AQC\Product&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;App\Models\Product&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;GetProduct&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="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&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="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&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;$params&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="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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'slug'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'slug'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'slug'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sku'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sku'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sku'&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="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;firstOrFail&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;Product can be fetched based on id, slug or sku.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Beware: Passing 2 parameters togather can result in not found.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Create a Record (CreateProduct)  
&lt;/h2&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\AQC\Product&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;App\Models\Product&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;CreateProduct&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;array&lt;/span&gt; &lt;span class="nv"&gt;$params&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;Product&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="nv"&gt;$params&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;The parameters are the source of truth. No mapping or rigid arguments required. Create is straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Update a Record Conditionally (UpdateProduct)  
&lt;/h2&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\AQC\Product&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;App\Models\Product&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;UpdateProduct&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="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$productObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Conditional WHERE clauses&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&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="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$productObj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&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;$params&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="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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;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="nv"&gt;$productObj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// update provided columns only&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$productObj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'columns'&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;blockquote&gt;
&lt;p&gt;One class handles updating multiple fields, conditionally, depending on what parameters exist. No need for separate methods like &lt;code&gt;updatePrice()&lt;/code&gt; or &lt;code&gt;updateStock()&lt;/code&gt;. &lt;br&gt;
 &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  5. Delete Records Conditionally (DeleteProduct)  
&lt;/h2&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\AQC\Product&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;App\Models\Product&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;DeleteProduct&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;array&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&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="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&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;$params&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="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="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_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;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'brand_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;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'brand_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'brand_id'&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="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&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;blockquote&gt;
&lt;p&gt;Conditional deletion using the same class for multiple delete intentions. Flexible, atomic, and predictable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Controller Example
&lt;/h2&gt;

&lt;p&gt;Using AQC inside a controller becomes very clean.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&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;index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetProducts&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;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;request&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;all&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;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.index'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products'&lt;/span&gt;&lt;span class="p"&gt;));&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;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$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;$product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GetProduct&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;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$id&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;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.index'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;));&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;store&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="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StoreProduct&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;handle&lt;/span&gt;&lt;span class="p"&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;all&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nf"&gt;session&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;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Product has been saved successfully.'&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;redirect&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;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.index'&lt;/span&gt;&lt;span class="p"&gt;);&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;update&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="nv"&gt;$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;$params&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;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$params&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="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UpdateProduct&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;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;session&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;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Product has been updated successfully.'&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;redirect&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;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.index'&lt;/span&gt;&lt;span class="p"&gt;);&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;destroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$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;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DeleteProduct&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;handle&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="nf"&gt;session&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;flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'message'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Product has been deleted successfully.'&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;redirect&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;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'products.index'&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;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Parameter-driven: No rigid methods. Flexible to multiple scenarios.&lt;/li&gt;
&lt;li&gt; Single class = single intention: Each class is atomic.&lt;/li&gt;
&lt;li&gt; Controllers stay thin: All query logic is inside AQC.&lt;/li&gt;
&lt;li&gt; No dependency injection required: Eloquent is enough, because these queries have fixed intentions.&lt;/li&gt;
&lt;li&gt; Reusable atomic filters: Common filters (e.g., active, status, role) can be moved into static helpers to be applied across multiple query classes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;AQC isn’t about complexity. It’s about clarity, atomicity, and flexibility.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Fetch multiple records? Use &lt;code&gt;GetProducts()&lt;/code&gt; with parameters.&lt;/li&gt;
&lt;li&gt;  Fetch a single record? Use &lt;code&gt;GetProduct()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Create, update, delete? Each has its own atomic class with conditional behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When applied correctly, AQC keeps controllers thin, and queries flexible. It scales with your application without creating method explosion or unnecessary abstraction layers.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>aqcdesignpattern</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Laravel Blade Partial Api Pattern</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Sat, 14 Mar 2026 17:40:57 +0000</pubDate>
      <link>https://dev.to/raheelshan/laravel-blade-partial-api-pattern-el</link>
      <guid>https://dev.to/raheelshan/laravel-blade-partial-api-pattern-el</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/raheelshan" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&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%2Fuser%2Fprofile_image%2F605559%2F6c4cc957-0c22-48fe-a1de-aa790e2cd394.jpg" alt="raheelshan"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/raheelshan/laravel-blade-partial-api-pattern-fetching-data-the-missing-part-4i24" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Laravel Blade Partial API Pattern: Fetching Data — The Missing Part&lt;/h2&gt;
      &lt;h3&gt;Raheel Shan ・ Nov 1 '25&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#programming&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#laravel&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#designpatterns&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>laravel</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Write One Query Class Instead of Many: The AQC Design Pattern for Laravel</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Thu, 12 Mar 2026 20:04:52 +0000</pubDate>
      <link>https://dev.to/raheelshan/write-one-query-class-instead-of-many-the-aqc-design-pattern-5f6l</link>
      <guid>https://dev.to/raheelshan/write-one-query-class-instead-of-many-the-aqc-design-pattern-5f6l</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%2F3kyt3nfyqqg91jj9b289.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%2F3kyt3nfyqqg91jj9b289.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;When I introduced the &lt;strong&gt;Atomic Query Construction (AQC)&lt;/strong&gt; design pattern, a few people asked: "Is this something new? Or is it just a renamed version of something that already exists?"&lt;/p&gt;

&lt;p&gt;Fair question. And the honest answer is: it's both.&lt;/p&gt;

&lt;p&gt;AQC isn't pulling ideas out of thin air. It's a deliberate combination of three well-established software design principles, packaged into a focused, practical approach to managing query logic. Once you see those foundations clearly, you'll understand not just what AQC does — but why it works.&lt;/p&gt;

&lt;p&gt;The three pillars are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Query Object Pattern&lt;/li&gt;
&lt;li&gt; Single Responsibility Principle (SRP)&lt;/li&gt;
&lt;li&gt; Pipeline / Composition Pattern&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let me walk through each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Query Object Pattern
&lt;/h2&gt;

&lt;p&gt;The Query Object pattern is a lesser-known but well-documented pattern from Martin Fowler's Patterns of Enterprise Application Architecture. The idea is straightforward: represent a database query as an object. Instead of scattering query logic across controllers, services, and helpers you encapsulate it in a dedicated class.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ActiveProducts&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;get&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;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&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;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&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;In AQC, the same idea is implemented. A dedicated class is created for specific type for operation. Fetch products for example. See, the class accepts parameters and returns results. The caller doesn't care how the query is built. It just calls the class and gets what it needs.&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\AQC\Product&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;App\Models\Product&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;GetProducts&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;static&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="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nv"&gt;$columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;latest&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="c1"&gt;// other code goes here&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;paginate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PAGINATE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$query&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;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;This is a query object. It has one job. It accepts inputs. It builds the query internally. It returns results.&lt;/p&gt;

&lt;p&gt;Instead of putting query logic inside controllers, repositories, or helpers, the query lives in its own class. Not a method buried in a repository. Not a static helper in some utility class. The query is the object. It has a name, a namespace, and a clear contract.&lt;/p&gt;

&lt;p&gt;What AQC adds on top of the raw Query Object pattern is the use of dynamic parameters and scenarios to handle multiple use cases within a single class. Instead of creating a separate class for &lt;code&gt;GetActiveProducts()&lt;/code&gt; and &lt;code&gt;GetProductsByCategory()&lt;/code&gt;, you have one &lt;code&gt;GetProducts()&lt;/code&gt; class that handles both and every combination through parameters.&lt;/p&gt;

&lt;p&gt;You call it from a controller:&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;// Admin panel&lt;/span&gt;
&lt;span class="nv"&gt;$columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GetProducts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="nv"&gt;$columns&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Frontend filtered by category&lt;/span&gt;
&lt;span class="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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;'active'&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="nv"&gt;$products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GetProducts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$columns&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Mobile API - out of stock report&lt;/span&gt;
&lt;span class="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'out_of_stock'&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="nv"&gt;$products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GetProducts&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$columns&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same class. Same method. Different parameters, different results. That's the &lt;strong&gt;Query Object pattern&lt;/strong&gt; doing its job.&lt;/p&gt;

&lt;p&gt;This is framework agnostic. The pattern doesn't care whether you're using Eloquent, Doctrine, SQLAlchemy, or raw PDO. The concept is the same: encapsulate the query in an object.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Single Responsibility Principle (SRP)
&lt;/h2&gt;

&lt;p&gt;SRP is one of the five SOLID principles. Robert C. Martin's definition is simple: a class should have one, and only one, reason to change.&lt;/p&gt;

&lt;p&gt;Most developers have heard this. But in practice, it gets violated constantly — especially with query logic. A service class ends up with &lt;code&gt;getActiveProducts()&lt;/code&gt;, &lt;code&gt;getProductsByCategory()&lt;/code&gt;, &lt;code&gt;getExpiredProducts()&lt;/code&gt;, and fifteen other methods. The class has dozens of reasons to change. That's the opposite of SRP.&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductRepository&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// base methods&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;create&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;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&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;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Product&lt;/span&gt; &lt;span class="nv"&gt;$product&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;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&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;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Product&lt;/span&gt; &lt;span class="nv"&gt;$product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&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;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?Product&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&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;all&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Collection&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// new fetch methods&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;getActiveProducts&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;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&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="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;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;getProductsByCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$categoryId&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;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$categoryId&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;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;getActiveProductsByCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$categoryId&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;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$categoryId&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;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;getProductsByStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$storeId&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;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'store_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$storeId&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;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;getActiveProductsByStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$storeId&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;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'active'&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'store_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$storeId&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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// and so on...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AQC enforces SRP at a structural level. Every query gets its own class. One class, one job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
└── AQC/
    └── Product/
        └── CreateProduct.php
        └── UpdateProduct.php
        └── GetAllProducts.php
        └── GetProduct.php           
        └── DeleteProduct.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GetProducts()&lt;/code&gt; only knows how to fetch a list of products. &lt;code&gt;StoreProduct()&lt;/code&gt; only knows how to save a new product. They don't bleed into each other. When a requirement changes — say, the listing query needs a new filter — you go to exactly one class and change exactly one thing.&lt;br&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\AQC\Product&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;App\Models\Product&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;GetProducts&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;static&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="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nv"&gt;$columns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;latest&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="c1"&gt;// other code goes here&lt;/span&gt;
        &lt;span class="c1"&gt;// always return products (more than one)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$query&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;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;That's SRP in practice.&lt;/p&gt;

&lt;p&gt;This might sound trivial, but it's the reason AQC scales. As your application grows, you add new classes, you don't bloat existing ones. The structure stays flat, discoverable, and predictable regardless of the size of your codebase.&lt;/p&gt;

&lt;p&gt;And this isn't Laravel-specific. Whether you're working in Node.js, Python, or .Net, the principle holds. One class, one query, one reason to change.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Pipeline / Composition Pattern
&lt;/h2&gt;

&lt;p&gt;This is the one people notice last, but it's what makes AQC genuinely powerful.&lt;/p&gt;

&lt;p&gt;Look at the &lt;code&gt;GetProducts()&lt;/code&gt; class below. The &lt;code&gt;handle()&lt;/code&gt; method doesn't do everything in one monolithic block. Internally, it's building the query in stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Apply filters&lt;/li&gt;
&lt;li&gt;  Select columns based on scenario&lt;/li&gt;
&lt;li&gt;  Apply sorting&lt;/li&gt;
&lt;li&gt;  Handle pagination&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each step is a distinct concern. In a more explicit implementation, these stages become private methods that each receive the query, add their piece, and pass it along.&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\AQC\Product&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;App\Models\Product&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;GetProducts&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;static&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="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nv"&gt;$columns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;latest&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="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;applyFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;selectColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;applySorting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'paginate'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handlePagination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$query&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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;applyFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;selectColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;applySorting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handlePagination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&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;This is a pipeline. The query object passes through a sequence of stages. Each stage does one thing. Each stage is independently readable, testable, and modifiable.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Composition pattern&lt;/strong&gt; takes this further — you build complex behavior by composing smaller, focused pieces rather than writing one massive function. In AQC, you're composing query conditions. In more advanced implementations, you could compose multiple AQC classes together when building complex reporting queries.&lt;/p&gt;

&lt;p&gt;A class that does ten things in one method is a class that will break in ten different ways. Split the stages. Compose the result.&lt;/p&gt;

&lt;p&gt;This is also why AQC classes are naturally easy to unit test. You're not testing a sprawling 200-line method. You're testing a pipeline where each stage has a clear input and output. You can test the filter stage independently. You can test the pagination stage independently. That's composition paying dividends.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Underlying Power of Composition Pattern
&lt;/h2&gt;

&lt;p&gt;One of the most powerful aspects of composition in AQC is how filters combine dynamically based on the parameters passed to the query. Each filter is applied conditionally, which means the query does not follow a fixed path. Instead, every parameter acts like a switch that may or may not add a constraint to the query. When multiple parameters are provided, the resulting query becomes a combination of all applicable filters. In practical terms, this creates a flexible query system where a single query class can support many possible filter combinations without needing dozens of specialized query methods. The behavior is similar to a Cartesian combination of conditions: any subset of parameters can be used together, and the query naturally adapts by composing only the relevant constraints. This keeps the code simple while still allowing a wide range of filtering scenarios.&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;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;applyFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&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 requested filters&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;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="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'category_id'&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'brand_id'&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'brand_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;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="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'brand_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'brand_id'&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyword'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'like'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyword'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&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="nf"&gt;orWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sku'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'like'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyword'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&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="nf"&gt;orWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'like'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'%'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'keyword'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&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;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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'low_stock'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stock'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;='&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'low_stock_point'&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'out_of_stock'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stock'&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="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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'expired'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;whereDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'expired_date'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;today&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;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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'featured'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'is_featured'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'trending'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'is_trending'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'product_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;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'related_product_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'product_id'&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="nv"&gt;$query&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;In a typical Repository or naive Query Object setup, every meaningful filter combination often turns into a separate method or class. Instead of one flexible query that reacts to parameters, developers end up writing many rigid query variants to cover different filtering needs. &lt;br&gt;&lt;br&gt;
Consider the above &lt;strong&gt;applyFilters(),&lt;/strong&gt; it would have been turned into the classes or repository methods listed below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;getProductsByCategory()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getProductsByBrand()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getProductsByCategoryAndBrand()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;searchProductsByKeyword()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;searchProductsByKeywordAndCategory()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;searchProductsByKeywordAndBrand()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;searchProductsByKeywordCategoryAndBrand()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getLowStockProducts()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getOutOfStockProducts()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getExpiredProducts()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getFeaturedProducts()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getTrendingProducts()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getProductsByCategoryAndLowStock()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getProductsByBrandAndLowStock()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getProductsByCategoryBrandAndLowStock()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getFeaturedProductsByCategory()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getTrendingProductsByBrand()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getExpiredProductsByCategory()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getLowStockProductsByBrand()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getProductsRelatedToProduct()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;getFeaturedProductsByCategoryAndBrand()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;searchFeaturedProductsByKeyword()&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;searchTrendingProductsByKeyword()&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why These Three, Together
&lt;/h2&gt;

&lt;p&gt;Individually, none of these patterns is a complete solution.&lt;/p&gt;

&lt;p&gt;SRP tells you to split things apart — but doesn't tell you how to organize query logic specifically.&lt;/p&gt;

&lt;p&gt;The Query Object pattern gives you the structural template — but doesn't enforce clean internal organization.&lt;/p&gt;

&lt;p&gt;The Pipeline / Composition pattern gives you internal clarity — but doesn't tell you how to structure the class's public contract.&lt;/p&gt;

&lt;p&gt;Together, they give you AQC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;SRP defines the boundary&lt;/strong&gt;: one class, one query.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Query Object pattern defines the structure&lt;/strong&gt;: a named class that encapsulates query construction.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Pipeline pattern defines the internals&lt;/strong&gt;: stages that compose to produce the final result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a pattern that's modular, reusable, testable, and predictable. Every query has a home. Every home has a consistent contract. Every contract is built in composable stages.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Class Naming and Static Methods
&lt;/h2&gt;

&lt;p&gt;One thing that may look slightly unconventional in this pattern is the class naming. In many object-oriented conventions, class names typically represent entities such as &lt;code&gt;Product&lt;/code&gt;, &lt;code&gt;Order&lt;/code&gt;, or &lt;code&gt;User&lt;/code&gt;. In AQC, however, the class name often represents an action or intent, such as &lt;code&gt;GetProducts()&lt;/code&gt;, &lt;code&gt;GetOrders()&lt;/code&gt;, or &lt;code&gt;GetUsers()&lt;/code&gt;. While this may feel unusual at first, it is not entirely new. Many established patterns, including Query Objects, Actions, and Command patterns, follow the same idea where a class represents a specific operation rather than a domain entity.&lt;/p&gt;

&lt;p&gt;Another stylistic choice in AQC is the use of static methods. Some developers prefer static entry points for simplicity, while others prefer instance methods for better dependency handling or testability. The pattern itself does not require one approach over the other. Teams are free to implement AQC using static or non-static methods depending on their coding standards and architectural preferences.  &lt;/p&gt;

&lt;h2&gt;
  
  
  The Framework Agonist Pattern
&lt;/h2&gt;

&lt;p&gt;I use Laravel, so the examples above use Eloquent. But AQC is not a Laravel pattern. It's a software design pattern that happens to be demonstrated with Laravel.&lt;/p&gt;

&lt;p&gt;You can apply the same thinking in Node.js, Python, .Net Framework or any language you use.&lt;/p&gt;

&lt;p&gt;The three underlying principles — SRP, Query Object, Pipeline/Composition are language-agnostic. AQC is simply an opinionated application of those three principles to the specific problem of managing query logic across a growing application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;When people see AQC for the first time, it looks deceptively simple. Just a class with a &lt;code&gt;handle()&lt;/code&gt; method. But the simplicity is intentional — it's what you get when three well-understood patterns come together cleanly.&lt;/p&gt;

&lt;p&gt;SRP says: give every query a dedicated home. Query Object says: make the query a first-class object with a clear contract. Pipeline/Composition says: build that query in stages, each one doing exactly one thing.&lt;/p&gt;

&lt;p&gt;That's AQC. Not magic. Not reinvention. Just three good ideas in the right combination.&lt;/p&gt;

&lt;p&gt;If you've been using these principles in your work already, AQC will feel like familiar ground with a concrete name and structure. If you haven't, this is a good place to start.&lt;/p&gt;

&lt;p&gt;Hope this article gives you new way of thinking and writing queries. Let me know what you think of it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>laravel</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Four Ways to Trigger Delete Row from a Table</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Sun, 08 Mar 2026 18:29:45 +0000</pubDate>
      <link>https://dev.to/raheelshan/four-ways-to-trigger-delete-row-from-a-table-43nn</link>
      <guid>https://dev.to/raheelshan/four-ways-to-trigger-delete-row-from-a-table-43nn</guid>
      <description>&lt;p&gt;Assume we have this route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::delete('/pages/{page}',
    [PageController::class, 'destroy']
)-&amp;gt;name('site.pages.destroy');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every delete must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Open confirmation modal&lt;/li&gt;
&lt;li&gt; Wait for user confirmation&lt;/li&gt;
&lt;li&gt; Then trigger DELETE request&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We are only discussing how the front end triggers the route.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. One Form Per Row
&lt;/h2&gt;

&lt;p&gt;Each row contains its own form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blade
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@foreach ($pages as $page)
&amp;lt;tr&amp;gt;
    &amp;lt;td&amp;gt;{{ $page-&amp;gt;title }}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
        &amp;lt;form id="delete-form-{{ $page-&amp;gt;id }}"
              action="{{ route('site.pages.destroy', $page-&amp;gt;id) }}"
              method="POST"&amp;gt;
            @csrf
            @method('DELETE')

            &amp;lt;button type="button"
                    onclick="openModal({{ $page-&amp;gt;id }})"&amp;gt;
                Delete
            &amp;lt;/button&amp;gt;
        &amp;lt;/form&amp;gt;
    &amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
@endforeach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
let selectedId = null;

function openModal(id) {
    selectedId = id;
    document.getElementById('delete-user-modal').classList.remove('hidden');
}

function submitDelete() {
    document
        .getElementById('delete-form-' + selectedId)
        .submit();
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What’s Happening
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Each row has its own form&lt;/li&gt;
&lt;li&gt;  Modal opens&lt;/li&gt;
&lt;li&gt;  On confirmation → that specific form submits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Works fine.&lt;/p&gt;

&lt;p&gt;If you render 300 rows, you now have 300 forms sitting in your DOM.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Ajax / Fetch DELETE Request
&lt;/h2&gt;

&lt;p&gt;Here, rows do not contain forms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blade
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@foreach ($pages as $page)
&amp;lt;tr id="row-{{ $page-&amp;gt;id }}"&amp;gt;
    &amp;lt;td&amp;gt;{{ $page-&amp;gt;title }}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
        &amp;lt;button type="button"
                onclick="openModal({{ $page-&amp;gt;id }})"&amp;gt;
            Delete
        &amp;lt;/button&amp;gt;
    &amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
@endforeach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Layout (CSRF Meta)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta name="csrf-token" content="{{ csrf_token() }}"&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
let selectedId = null;

function openModal(id) {
    selectedId = id;
    document.getElementById('delete-user-modal').classList.remove('hidden');
}

function submitDelete() {

    fetch(`/pages/${selectedId}`, {
        method: 'DELETE',
        headers: {
            'X-CSRF-TOKEN': document
                .querySelector('meta[name="csrf-token"]')
                .getAttribute('content'),
            'Content-Type': 'application/json'
        }
    }).then(response =&amp;gt; {
        if (response.ok) {
            document.getElementById('row-' + selectedId).remove();
        }
    });
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What’s Happening
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  Modal confirms&lt;/li&gt;
&lt;li&gt;  JavaScript sends DELETE request&lt;/li&gt;
&lt;li&gt;  On success → remove row from DOM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No page reload. Clean UI.&lt;/p&gt;

&lt;p&gt;But you are now responsible for handling errors properly.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. One Form Wrapping the Table
&lt;/h2&gt;

&lt;p&gt;Instead of many forms, use one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blade
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form id="delete-form" method="POST"&amp;gt;
    @csrf
    @method('DELETE')

    &amp;lt;input type="hidden" name="page" id="delete-id"&amp;gt;

    &amp;lt;table&amp;gt;
        @foreach ($pages as $page)
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;{{ $page-&amp;gt;title }}&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;
                &amp;lt;button type="button"
                        onclick="openModal({{ $page-&amp;gt;id }})"&amp;gt;
                    Delete
                &amp;lt;/button&amp;gt;
            &amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        @endforeach
    &amp;lt;/table&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
let selectedId = null;

function openModal(id) {
    selectedId = id;
    document.getElementById('delete-user-modal').classList.remove('hidden');
}

function submitDelete() {

    const form = document.getElementById('delete-form');

    form.action = `/pages/${selectedId}`;
    form.submit();
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What’s Happening
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  One form&lt;/li&gt;
&lt;li&gt;  Hidden input stores ID&lt;/li&gt;
&lt;li&gt;  Action set dynamically&lt;/li&gt;
&lt;li&gt;  Full page reload&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cleaner than method one. Still traditional submission.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Hidden Global Form + Route Placeholder (Preferred)
&lt;/h2&gt;

&lt;p&gt;This is my pattern.&lt;/p&gt;

&lt;p&gt;One hidden form. Outside the table. No ID in the initial action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hidden Form
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form method="POST"
      action="{{ route('site.pages.destroy', ['page' =&amp;gt; 0]) }}"
      id="delete-form"
      class="hidden"&amp;gt;
    @csrf
    @method('DELETE')
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice:&lt;/p&gt;

&lt;p&gt;We deliberately pass &lt;code&gt;0&lt;/code&gt; as a placeholder.&lt;/p&gt;

&lt;p&gt;Laravel generates a valid route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/pages/0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will replace &lt;code&gt;0&lt;/code&gt; dynamically.&lt;/p&gt;




&lt;h3&gt;
  
  
  Delete Button
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;button type="button"
        data-modal-target="delete-user-modal"
        data-modal-toggle="delete-user-modal"
        onclick="setID({{ $page-&amp;gt;id }})"&amp;gt;
    Delete
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
let selectedId = null;

function setID(id) {

    selectedId = id;

    let path = "{{ route('site.pages.destroy', ['page' =&amp;gt; 0]) }}";

    path = path.replace('/0', '/' + id);

    document.getElementById('delete-form').action = path;
}

function submitForm() {
    document.getElementById('delete-form').submit();
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Modal Confirmation Button
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="#" onclick="submitForm()"&amp;gt;Yes&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  What’s Happening
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Route is generated once with placeholder &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; When delete button is clicked → ID is captured&lt;/li&gt;
&lt;li&gt; JavaScript replaces &lt;code&gt;/0&lt;/code&gt; with real ID&lt;/li&gt;
&lt;li&gt; Confirmation button submits the hidden form&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  One form in DOM&lt;/li&gt;
&lt;li&gt;  Clean table markup&lt;/li&gt;
&lt;li&gt;  Proper Laravel route usage&lt;/li&gt;
&lt;li&gt;  No route string hardcoded&lt;/li&gt;
&lt;li&gt;  Centralized delete logic&lt;/li&gt;
&lt;li&gt;  Modal confirmation built-in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Structurally, this is clean and scalable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus: The HTMX Way
&lt;/h2&gt;

&lt;p&gt;If you’re using HTMX, you don’t need custom fetch calls, and you don’t need to manually submit hidden forms.&lt;/p&gt;

&lt;p&gt;HTMX lets you trigger HTTP requests directly from HTML attributes.&lt;/p&gt;

&lt;p&gt;Yes. Just attributes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Basic HTMX Delete Example
&lt;/h2&gt;

&lt;p&gt;First, include HTMX in your layout:&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;script src="https://unpkg.com/htmx.org@1.9.10"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;meta name="csrf-token" content="{{ csrf_token() }}"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@foreach ($pages as $page)
&amp;lt;tr id="row-{{ $page-&amp;gt;id }}"&amp;gt;
    &amp;lt;td&amp;gt;{{ $page-&amp;gt;title }}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
        &amp;lt;button 
            hx-delete="{{ route('site.pages.destroy', $page-&amp;gt;id) }}"
            hx-target="#row-{{ $page-&amp;gt;id }}"
            hx-swap="outerHTML"
            hx-confirm="Are you sure you want to delete this page?"
            hx-headers='{"X-CSRF-TOKEN": "{{ csrf_token() }}"}'
        &amp;gt;
            Delete
        &amp;lt;/button&amp;gt;
    &amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
@endforeach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What’s Happening
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;hx-delete&lt;/code&gt; sends a DELETE request&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;hx-confirm&lt;/code&gt; shows confirmation popup automatically&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;hx-target&lt;/code&gt; tells HTMX which element to update&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;hx-swap="outerHTML"&lt;/code&gt; removes the row after successful response&lt;/li&gt;
&lt;li&gt;  CSRF token is passed via header&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No custom JavaScript required.&lt;/p&gt;

&lt;p&gt;No hidden forms.&lt;/p&gt;

&lt;p&gt;No manual fetch logic.&lt;/p&gt;

&lt;p&gt;Just attributes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Method Is Better?
&lt;/h2&gt;

&lt;p&gt;With confirmation required, method one becomes noisy because you must track many forms.&lt;/p&gt;

&lt;p&gt;Method three is acceptable but still tied to page reload behavior.&lt;/p&gt;

&lt;p&gt;Ajax is powerful if you want instant UI updates.&lt;/p&gt;

&lt;p&gt;But from a structural Laravel perspective, the hidden global form with placeholder replacement is the most balanced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Clean Blade&lt;/li&gt;
&lt;li&gt;  Clean routes&lt;/li&gt;
&lt;li&gt;  Clean confirmation flow&lt;/li&gt;
&lt;li&gt;  Minimal DOM pollution&lt;/li&gt;
&lt;li&gt;  Easy to maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It separates concerns properly.&lt;/p&gt;

&lt;p&gt;Table renders data. Modal handles confirmation. Hidden form handles submission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why HTMX Is Interesting
&lt;/h2&gt;

&lt;p&gt;It gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  AJAX behavior&lt;/li&gt;
&lt;li&gt;  Confirmation&lt;/li&gt;
&lt;li&gt;  DOM update&lt;/li&gt;
&lt;li&gt;  RESTful requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without writing JavaScript logic yourself.&lt;/p&gt;

&lt;p&gt;It keeps your Blade readable.&lt;/p&gt;

&lt;p&gt;It keeps your routes clean.&lt;/p&gt;

&lt;p&gt;It feels like traditional server-driven apps, but dynamic.  &lt;/p&gt;

&lt;p&gt;Hope this gives you clearer picture what you can adopt to delete a row.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>laravel</category>
      <category>php</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Laravel Blade Partial API Pattern: Fetching Data — The Missing Part</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Sat, 01 Nov 2025 11:18:54 +0000</pubDate>
      <link>https://dev.to/raheelshan/laravel-blade-partial-api-pattern-fetching-data-the-missing-part-4i24</link>
      <guid>https://dev.to/raheelshan/laravel-blade-partial-api-pattern-fetching-data-the-missing-part-4i24</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/raheelshan/laravel-blade-partial-api-pattern-with-htmx-3aj"&gt;previous article&lt;/a&gt;, we built the foundation for what I called &lt;strong&gt;Laravel Blade Partial API Pattern&lt;/strong&gt;. The idea was simple—to make every Blade partial directly accessible through an API-style URL using HTMX, without writing individual controller methods or route definitions.&lt;/p&gt;

&lt;p&gt;That worked fine until we hit one problem: &lt;strong&gt;data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The route knew how to locate and render a partial, but it didn’t know what data that partial needed. Some partials required a single record (like a product card), others needed multiple records (like a product list), and a few needed data built from complex DTO classes. This article is about that missing piece—how to fetch and pass the correct data automatically.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Problem Recap
&lt;/h3&gt;

&lt;p&gt;The old global route could do this much:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/products/partials/product-card/2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;→ find &lt;code&gt;`resources/views/products/partials/product-card.blade.php`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;→ load view&lt;/p&gt;

&lt;p&gt;→ done&lt;/p&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;It didn’t care whether that partial needed one product, a list of products, or a composed object with multiple models. We’re now going to fix that.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Rules (The Contract Between View and Data)
&lt;/h3&gt;

&lt;p&gt;To make the system predictable and maintainable, there need to be rules. After experimenting, I came up with six clear steps that define how a partial route resolves data.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Find the correct partial to load Every request like &lt;code&gt;`/products/partials/product-card/2`&lt;/code&gt; should map directly to &lt;code&gt;`resources/views/products/partials/product-card.blade.php`&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Find the resource from the first segment The first segment &lt;code&gt;`products`&lt;/code&gt; is always the model reference. It’s converted to singular form &lt;code&gt;`product`&lt;/code&gt; to locate the model: &lt;code&gt;`App\Models\Product`&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Decide if one or more records are required This comes from the filename prefix. Starts with &lt;code&gt;`products-`&lt;/code&gt; → multiple records Starts with &lt;code&gt;`product-`&lt;/code&gt; → single record&lt;/li&gt;
&lt;li&gt; Extract the DTO or ViewModel annotation At the top of each partial, I can define what data structure it expects:&lt;code&gt;`/** @var \App\ViewModels\ProductCardDTO $model */.`&lt;/code&gt; This tells the route what class to instantiate and which columns the AQC should select.&lt;/li&gt;
&lt;li&gt; Fetch the data using AQC Once the DTO is known, the system calls the AQC Design Pattern class to select the appropriate columns and fetch records from the corresponding model.&lt;/li&gt;
&lt;li&gt; Return the rendered HTML Finally, the resolved data is passed to the partial and returned as raw HTML — ideal for HTMX swaps or inline updates.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Example in Action
&lt;/h3&gt;

&lt;p&gt;Let’s take these partials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/resources/views/products/partials/products-list.blade.php  // multiple
/resources/views/products/partials/product-card.blade.php   // single
/resources/views/products/partials/product-row.blade.php    // single
/resources/views/products/partials/product-quick-view.blade.php // single
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And these URLs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/products/partials/products-list 
/products/partials/product-card/2
/products/partials/product-row/2 
/products/partials/product-quick-view/2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s the global route refactored.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use App\Helpers\PartialApiResolver;

Route::match(['get', 'post'], '{path}', function(string $path, Request $request){
    return App\Helpers\PartialApiResolver::handle($path, $request);
})-&amp;gt;where('path', '.*');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's the helper class with all 6 steps.&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;?php
namespace App\Helpers;

use Illuminate\Http\Request;
use Illuminate\Support\Str;

class PartialApiResolver
{
    public static function handle(string $path, Request $request)
    {
        $path = trim($path, '/');
        if ($path === '') abort(404);

        $segments = explode('/', $path);
        $id = self::extractId($segments);
        $viewPath = self::viewPath($segments);
        $resourceName = self::detectResource($segments);
        $dtoClass = self::extractDto($segments);
        $data = self::fetchData($resourceName, $id, $dtoClass, $request);

        return view($viewPath, $data);
    }

    private static function extractId(array &amp;amp;$segments): ?int
    {
        $maybeLast = end($segments);
        if (is_numeric($maybeLast)) {
            array_pop($segments);
            return (int) $maybeLast;
        }
        return null;
    }

    private static function viewPath(array $segments): string
    {
        return implode('.', $segments);
    }

    private static function detectResource(array $segments): ?string
    {
        $resourceSegment = $segments[0] ?? null;
        return $resourceSegment ? Str::singular($resourceSegment) : null;
    }

    private static function extractDto(array $segments): ?string
    {
        $bladeFile = resource_path('views/' . implode('/', $segments) . '.blade.php');
        if (!file_exists($bladeFile)) return null;

        $contents = file_get_contents($bladeFile);
        if (preg_match('/@var\s+([A-Za-z0-9_\\\\&amp;lt;&amp;gt;]+)\s+\$[A-Za-z0-9_]+/m', $contents, $m)) {
            return trim($m[1]);
        }
        return null;
    }

    private static function fetchData(?string $resource, ?int $id, ?string $dto, Request $request): array
    {
        if (!$resource) return [];
        $aqcNamespace = "App\\AQC\\" . ucfirst($resource);
        $columns = $dto &amp;amp;&amp;amp; method_exists($dto, 'columns') ? $dto::columns() : '*';

        try {
            if ($id) {
                $aqcClass = "{$aqcNamespace}\\Get" . ucfirst($resource);
                if (!class_exists($aqcClass)) abort(404, "AQC class [$aqcClass] not found.");

                $item = $aqcClass::handle($id, $request-&amp;gt;all(), $columns);
                return [$resource =&amp;gt; $item ?: new ("App\\Models\\" . ucfirst($resource))];
            } else {
                $aqcClass = "{$aqcNamespace}\\Get" . Str::plural($resource);
                if (!class_exists($aqcClass)) abort(404, "AQC class [$aqcClass] not found.");

                $items = $aqcClass::handle($request-&amp;gt;all(), $columns);
                return [Str::plural($resource) =&amp;gt; $items];
            }
        } catch (\Throwable $e) {
            return $id
                ? [$resource =&amp;gt; new ("App\\Models\\" . ucfirst($resource))]
                : [Str::plural($resource) =&amp;gt; collect()];
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Now that you know how we load partials, let’s look at how we prepare their data at a structural level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Note before you move on
&lt;/h2&gt;

&lt;p&gt;To fully understand why I’m using DTO classes and the Atomic Query Construction (AQC) pattern in this setup, don’t skip this part. Both are essential to how the Partial API pattern works under the hood. DTOs define the structure of data your Blade partial expects, while AQC defines how that data is fetched. The rest of this article will make a lot more sense once you grasp both pieces—so read carefully.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Using DTO Classes with Blade Partials
&lt;/h3&gt;

&lt;p&gt;In this pattern, every Blade partial should know exactly what data structure it’s getting — and that’s where DTO classes come in. Instead of passing a random collection of variables from your controller or resolver, define a dedicated DTO (or ViewModel) that represents the data contract for that specific partial. For example, a &lt;code&gt;ProductCardDTO&lt;/code&gt; might expose only &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, and &lt;code&gt;imageUrl&lt;/code&gt;. This makes your Blade file predictable, testable, and IDE-friendly. If you’ve never structured your Blade files around contracts before, you can read my detailed explanation here: 👉 &lt;a href="https://dev.to/raheelshan/stop-treating-your-blade-files-like-trash-bins-give-them-contracts-and-structure-43e9"&gt;Don’t Pass Array or Variables to Laravel Blade Views Instead Do This&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That article shows how to make each Blade file behave like a component with a well-defined interface, keeping logic where it belongs and leaving the template clean. Once you apply that mindset, the Partial API pattern becomes far more maintainable because every endpoint and partial communicates through a stable contract instead of loose variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Role of the AQC Design Pattern
&lt;/h3&gt;

&lt;p&gt;Behind the DTOs sits the Atomic Query Construction (AQC) pattern. AQC lets you build reusable, composable query classes that encapsulate all data-fetching logic. Instead of sprinkling &lt;code&gt;Model::query()&lt;/code&gt; all over your project, each AQC class handles one atomic concern — fetching users, filtering products, calculating stats — and can be reused across controllers, APIs, and partials.&lt;/p&gt;

&lt;p&gt;When you combine AQC with DTOs, the flow becomes clean and layered: AQC → DTO → Blade Partial. Your AQC class produces the data. The DTO shapes it. The partial displays it.&lt;/p&gt;

&lt;p&gt;You can explore the full pattern in this article: 👉 &lt;a href="https://dev.to/raheelshan/introducing-the-atomic-query-construction-aqc-design-pattern-25l9"&gt;Introducing the Atomic Query Construction (AQC) Design Pattern&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That post dives deeper into why atomic queries outperform ad-hoc query logic and how they integrate naturally with the Partial API pattern described here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation Notes
&lt;/h3&gt;

&lt;p&gt;Here's what we have done so far.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Detect plural or singular pattern to decide between collection or single model.&lt;/li&gt;
&lt;li&gt;  Check for numeric ID in the URL (if found, load a single record).&lt;/li&gt;
&lt;li&gt;  Parse the partial file to extract the DTO or ViewModel class name from annotations.&lt;/li&gt;
&lt;li&gt;  Pass model and DTO into your AQC pipeline to build queries dynamically.&lt;/li&gt;
&lt;li&gt;  Graceful fallback: if a record is missing, provide a new model instance instead of throwing an error.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way, even if you hit a partial like &lt;code&gt;/product/partials/product-card/9999&lt;/code&gt;, it still renders without breaking the flow.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why This Works So Well
&lt;/h3&gt;

&lt;p&gt;Because it combines the predictability of Laravel’s Blade conventions with the flexibility of APIs. HTMX requests behave like AJAX calls but stay fully server-driven. Your AQC pattern handles the data efficiently. And your Blade partials now serve as self-contained, declarative view components that know what data they need.&lt;/p&gt;

&lt;p&gt;You don’t have to touch controllers, define routes, or mix JSON and HTML responses. The system just knows what to do.&lt;/p&gt;




&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;This pattern now feels complete. The first part gave partials their own routes. This part gave them data.&lt;/p&gt;

&lt;p&gt;What you have now is a unified layer where every Blade partial can act like an API endpoint — with model discovery, DTO integration, and automatic data fetching.&lt;/p&gt;

&lt;p&gt;This is what I have experienced recently. What is your take on this approach? Let's hear your ideas and thoughts.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>laravel</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Laravel Blade Partial API Pattern with HTMX</title>
      <dc:creator>Raheel Shan</dc:creator>
      <pubDate>Mon, 20 Oct 2025 11:48:56 +0000</pubDate>
      <link>https://dev.to/raheelshan/laravel-blade-partial-api-pattern-with-htmx-3aj</link>
      <guid>https://dev.to/raheelshan/laravel-blade-partial-api-pattern-with-htmx-3aj</guid>
      <description>&lt;p&gt;We have seen splendid UIs in this latest era. &lt;em&gt;All thanks to Javascript, which has snatched the rendering responsibility from the backend that was always good at doing it.&lt;/em&gt; But it has also come with some cost. On the way to achieving SPA, reactivity, and interactivity, we have faced the following issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SEO Compromised
&lt;/h2&gt;

&lt;p&gt;This is the most important issue. When users or crawlers come to visit our page, the page is empty with a div with an id may be &lt;code&gt;root&lt;/code&gt;. Users wait for the page to be loaded, but crawlers go back, marking this page empty without data. This heavily affects the ranking in search engines. To counter this issue, people started to use static site generation. Now that the pages are statically generated, they are not updated frequently, yet another problem to be solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Markup Drift
&lt;/h2&gt;

&lt;p&gt;The real issue isn’t JSX syntax — it’s that markup moved to the JavaScript side. Once that happened, the frontend had to take over everything the backend used to handle effortlessly.&lt;/p&gt;

&lt;p&gt;Now the UI stack needs a build pipeline before a single element can render. JSX won’t run on its own, so tools like Babel, Vite, and Webpack become mandatory. Then come TypeScript, linters, formatters, and dependency managers — a full ecosystem just to output HTML.&lt;/p&gt;

&lt;p&gt;This shift adds layers of fragility. Every change passes through a build; every runtime error risks a blank screen. The frontend must now fetch data, construct markup, manage reactivity, and coordinate state — all inside one sensitive language runtime.&lt;/p&gt;

&lt;p&gt;And because markup no longer comes from the server, everything must be serialized into JSON first. The browser becomes responsible for turning that JSON back into visible UI. Two systems — backend and frontend — must now agree on the same shape of data and structure of views.&lt;/p&gt;

&lt;p&gt;We didn’t simplify web development; we just moved the complexity closer to the user’s browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Cost of JavaScript-Controlled Rendering
&lt;/h2&gt;

&lt;p&gt;When JavaScript took over rendering, the web quietly stopped being simple. Every framework—React, Vue, Svelte, even the old Knockout and Backbone—started rebuilding what the browser was already doing just fine: creating and updating the DOM. Now, instead of writing HTML, frontend engineers spend their time being part-time compiler engineers—setting up build tools, handling hydration, debugging mismatched markup, and wrestling with virtual DOMs. What used to be a plain document has turned into a fragile choreography between server and client, where one small issue can break rendering, kill performance, or confuse search engines. We ended up building complex systems mostly to fix problems introduced by the same complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Avoid JavaScript Hell
&lt;/h2&gt;

&lt;p&gt;Since we have given JavaScript the responsibility to render/hydrate the frontend and have stuck with what we have experienced, what if we take back the responsibility from JavaScript and make it a little simple? Let Javascript only do what it is best at. We are simply going to use a few techniques.&lt;/p&gt;

&lt;p&gt;Let's start step by step. First we are going to load a full page with Laravel Blade.&lt;/p&gt;

&lt;p&gt;Let's say we need to display a list of products in a table. Each row will have a refresh button. A very basic example. We will do the below code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ProductController extends Controller
{
    public function index(Request $request)
    {
        $products = Product::paginate(10);
        return view('product.index', compact('products'));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's load the blade view.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// resources/views/product/index.blade.php
@extends('layouts.app')

@section('content')
&amp;lt;div class="container"&amp;gt;
    &amp;lt;h2&amp;gt;Products&amp;lt;/h2&amp;gt;
    &amp;lt;table&amp;gt;
        &amp;lt;thead&amp;gt;
            &amp;lt;tr&amp;gt;
                &amp;lt;th scope="col"&amp;gt;Product&amp;lt;/th&amp;gt;
                &amp;lt;th scope="col"&amp;gt;Category&amp;lt;/th&amp;gt;
                &amp;lt;th scope="col"&amp;gt;Brand&amp;lt;/th&amp;gt;
                &amp;lt;th scope="col"&amp;gt;Actions&amp;lt;/th&amp;gt;
            &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody&amp;gt;
            @if (isset($products) &amp;amp;&amp;amp; count($products) &amp;gt; 0)
                @foreach ($products as $product)
                    @include('product.partials.table.row',['product' =&amp;gt; $product])
                @endforeach
            @endif

        &amp;lt;/tbody&amp;gt;
    &amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
@endsection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do pay attention to how we are adding a partial inside loop.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You must load HTMX in your page before using it. &lt;a href="https://htmx.org/docs/#installing" rel="noopener noreferrer"&gt;HTMX installation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//resources/views/product/partials/table/row.blade.php
&amp;lt;tr id="product-{{ $product-&amp;gt;id }}"&amp;gt;
    &amp;lt;td&amp;gt;{{ $product-&amp;gt;name }}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;{{ $product-&amp;gt;category }}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;{{ $product-&amp;gt;brand }}&amp;lt;/td&amp;gt;
    &amp;lt;td&amp;gt;
        &amp;lt;button 
            type="button"
            hx-get="{{ url('product/partials/table/row/'. $product-&amp;gt;id) }}" 
            hx-target="#product-{{ $product-&amp;gt;id }}"
            hx-swap="outerHTML" 
            class="btn btn-sm btn-primary mb-3"&amp;gt;
            Refresh
        &amp;lt;/button&amp;gt;    
    &amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here we are telling HTMX to refresh the current product row. We have provided it with a unique ID &lt;code&gt;product-x&lt;/code&gt; and an API endpoint to fetch partial from.&lt;/p&gt;

&lt;p&gt;Now let's move to Laravel routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Global Arrival Route Setup
&lt;/h2&gt;

&lt;p&gt;To make this pattern fully reusable across any part of the application, we define a single global route that will handle all incoming requests for Blade partials through HTMX.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Illuminate\Support\Str;
use Illuminate\Support\Facades\View;
use Illuminate\Http\Request;

Route::match(['get', 'post'], '{path}', function ($path, Request $request) {
    // convert "product/partials/table/row" to "product.partials.table.row"
    $viewPath = str_replace('/', '.', $path);

    // ensure view exists
    if (!View::exists($viewPath)) {
        abort(404, "Partial [$viewPath] not found.");
    }

    // split segments for context
    $segments = explode('/', $path);

    // detect model name (singular or plural)
    $modelSegment = $segments[0] ?? null;
    $modelName = ucfirst(Str::singular($modelSegment));
    $modelClass = "App\\Models\\$modelName";

    $data = [];

    // only attempt model binding if model class exists
    if (class_exists($modelClass)) {
        // if last segment is numeric, assume it’s an ID → single item
        $last = end($segments);
        if (is_numeric($last)) {
            $data = [Str::lower($modelName) =&amp;gt; $modelClass::findOrFail($last)];
        }
        // if plural model segment detected → fetch collection
        elseif (Str::plural($modelSegment) === $modelSegment) {
            $data = [Str::lower(Str::plural($modelName)) =&amp;gt; $modelClass::all()];
        }
    }

    return view($viewPath, $data);
})-&amp;gt;where('path', '.*');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This route is responsible for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Handling both GET and POST requests so partials can support form submissions or dynamic interactions.&lt;/li&gt;
&lt;li&gt; Parsing the requested URL to determine which Blade partial should be loaded.&lt;/li&gt;
&lt;li&gt; Loading any dynamic data required by that partial. (This is currently experimental.)&lt;/li&gt;
&lt;li&gt; Returning a fully Blade-rendered HTML partial back to HTMX.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This global route acts like a content dispatcher. Once it is configured, any partial inside your views directory becomes instantly accessible through HTMX calls, simply by referencing its path as a URL. It removes repetitive route creation and keeps your backend clean while enabling front-end interactivity powered by Blade and HTMX.&lt;/p&gt;

&lt;p&gt;HTMX request → Arrival Route → Resolve Partial → Fetch Data → Return Rendered HTML&lt;/p&gt;

&lt;p&gt;With this approach in place, you can focus on building partials and dynamic UI behavior without needing dedicated routes for every small UI update.&lt;/p&gt;




&lt;h3&gt;
  
  
  Another example.
&lt;/h3&gt;

&lt;p&gt;Let's say we need to refresh the whole table this time and we are defining a button to refresh the table data. In this case, here is the example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@extends('layouts.app')

@section('content')
&amp;lt;div class="container"&amp;gt;
    &amp;lt;h2&amp;gt;Products&amp;lt;/h2&amp;gt;
    &amp;lt;button 
        type="button"
        hx-get="{{ url('product/partials/table') }}" 
        hx-target="#product-rows"
        hx-swap="innerHTML" 
        class="btn btn-sm btn-primary mb-3"&amp;gt;
        Refresh Table
    &amp;lt;/button&amp;gt;       
    &amp;lt;table&amp;gt;
        &amp;lt;thead&amp;gt;
            &amp;lt;tr&amp;gt;
                &amp;lt;th scope="col"&amp;gt;Product&amp;lt;/th&amp;gt;
                &amp;lt;th scope="col"&amp;gt;Category&amp;lt;/th&amp;gt;
                &amp;lt;th scope="col"&amp;gt;Brand&amp;lt;/th&amp;gt;
                &amp;lt;th scope="col"&amp;gt;Actions&amp;lt;/th&amp;gt;
            &amp;lt;/tr&amp;gt;
        &amp;lt;/thead&amp;gt;
        &amp;lt;tbody id="product-rows"&amp;gt;
            @include('products.partials.table.list',['products' =&amp;gt; $products])
        &amp;lt;/tbody&amp;gt;
    &amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;
@endsection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And again, extract the partial where needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//resources/views/product/partials/table/list.blade.php

@if (isset($products) &amp;amp;&amp;amp; count($products) &amp;gt; 0)
    @foreach ($products as $product)
        @include('products.partials.table.row',['product' =&amp;gt; $product])
    @endforeach
@endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this setup we need to respect some rules. Here we go.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rule 1—Directory equals URL
&lt;/h4&gt;

&lt;p&gt;Every request path directly maps to a Blade file under &lt;code&gt;resources/views&lt;/code&gt;. &lt;code&gt;/product/partials/form&lt;/code&gt; → &lt;code&gt;resources/views/product/partials/form.blade.php&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Rule 2 — Dots replace slashes
&lt;/h4&gt;

&lt;p&gt;Internally, Laravel views use dot notation, so the route simply replaces &lt;code&gt;/&lt;/code&gt; with &lt;code&gt;.&lt;/code&gt; before rendering.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rule 3 — Any depth allowed
&lt;/h4&gt;

&lt;p&gt;There’s no fixed folder depth. The resolver supports nested structures like &lt;code&gt;/product/partials/table/row&lt;/code&gt; → &lt;code&gt;product.partials.table.row&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Rule 4 — No model or data assumptions
&lt;/h4&gt;

&lt;p&gt;The resolver doesn’t inject or infer data. Every partial is responsible for its own context (passed from the parent view or controller).&lt;/p&gt;

&lt;h4&gt;
  
  
  Rule 5 — 404 for missing partials
&lt;/h4&gt;

&lt;p&gt;If the resolved view doesn’t exist, the route gracefully fails with a 404. No silent fallbacks, no guesswork.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rule 6 — Both GET and POST supported
&lt;/h4&gt;

&lt;p&gt;The same route handles &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt;, so it can respond to &lt;code&gt;hx-get&lt;/code&gt;, &lt;code&gt;hx-post&lt;/code&gt;, or form submissions without extra definitions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rule 7 — Everything is a partial endpoint
&lt;/h4&gt;

&lt;p&gt;Any Blade file—model-specific or global—can act as an endpoint. You’re free to mix singular (product) or plural (products) folders; the router doesn’t care.&lt;/p&gt;




&lt;h2&gt;
  
  
  How This Solves the SEO Problem
&lt;/h2&gt;

&lt;p&gt;When the page first loads, Laravel renders everything—a fully formed HTML document with all partials in place. Search engines see a complete, server-rendered page.&lt;/p&gt;

&lt;p&gt;After that, HTMX takes over. The same partials that were included during the initial load can now be reloaded individually via the same Blade templates and route pattern.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The page is crawlable and SEO-friendly.&lt;/li&gt;
&lt;li&gt;  The partials are reusable.&lt;/li&gt;
&lt;li&gt;  You’re not duplicating frontend logic or templates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Pattern Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  No duplication. The same Blade partials power both full-page and partial loads.&lt;/li&gt;
&lt;li&gt;  SEO intact. The first render is full HTML, not JavaScript-rendered fluff.&lt;/li&gt;
&lt;li&gt;  Reusable. Any component can be loaded standalone or as part of a bigger page.&lt;/li&gt;
&lt;li&gt;  Simple routing. One global route pattern controls all partial endpoints.&lt;/li&gt;
&lt;li&gt;  No JSON. The browser never has to assemble UI from raw data again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is Laravel doing what Laravel does best: serving clean HTML from the server, enhanced with just enough JavaScript to feel dynamic.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simpler Future
&lt;/h2&gt;

&lt;p&gt;The Blade Partial API Pattern isn’t a framework or a library. It’s just &lt;strong&gt;Laravel used properly&lt;/strong&gt;—with &lt;strong&gt;HTMX&lt;/strong&gt; as the final piece that gives your pages dynamic behavior without losing their soul. It’s fast, indexable, and human to read. Server-driven UI, but done right. No build tools, no JSON, no magic—just Blade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The beauty of this pattern is that every request still flows through Laravel’s full stack—middleware, validation, gates, and permissions. You’re not giving up structure or safety for convenience. Each Blade partial can be protected, validated, or even transformed the same way as any API route. The difference is that the response isn’t JSON—it’s ready-to-render markup, straight from the server, exactly as Laravel intended.&lt;/p&gt;

&lt;p&gt;It’s not about going backward. It’s about taking what’s already powerful and using it as it was meant to be used—without pretending the browser needs to be a compiler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ending Notes
&lt;/h2&gt;

&lt;p&gt;This article is experimental, and I am going to do a bit more research so we can complete all scenarios. if you are interested. Subscribe to my blog and stay tuned. &lt;/p&gt;

</description>
      <category>laravel</category>
      <category>api</category>
      <category>javascript</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
