<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Blog | Punyapal Shah</title>
        <link>https://mrpunyapal.dev/blog</link>
        <atom:link href="https://mrpunyapal.dev/blog/feed.xml" rel="self" type="application/rss+xml"/>
        <description>Articles, tutorials, and engineering guides for Laravel, PHP, Git, and modern web development by Punyapal Shah.</description>
        <language>en-US</language>
        <lastBuildDate>Thu, 03 Sep 2026 00:00:00 GMT</lastBuildDate>
        <managingEditor>contact@mrpunyapal.dev (Punyapal Shah)</managingEditor>
        <webMaster>contact@mrpunyapal.dev (Punyapal Shah)</webMaster>
        <item>
            <title><![CDATA[Use Immutable Dates by Default in Laravel]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-carbon-immutable-dates</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-carbon-immutable-dates</guid>
            <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Configure Laravel's Date facade to use CarbonImmutable by default, and automate migrating legacy Carbon calls with Rector.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Configure Laravel&#39;s Date facade to use CarbonImmutable by default, and automate migrating legacy Carbon calls with Rector.</p>
</blockquote>
<p>By default, Carbon instances in PHP are mutable. Modifying a date instance unexpectedly alters the original variable:</p>
<pre><code class="language-php">$date = now();

$tomorrow = $date-&gt;addDay();

// Both variables now point to tomorrow!
echo $date-&gt;toDateString();     // 2026-09-04
echo $tomorrow-&gt;toDateString(); // 2026-09-04</code></pre><hr>
<h2>1. Configure the Date Facade</h2>
<p>In <code>AppServiceProvider::boot()</code>, instruct Laravel&#39;s <code>Date</code> factory to generate <code>CarbonImmutable</code> instances:</p>
<pre><code class="language-php">namespace App\Providers;

use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Date::use(CarbonImmutable::class);
    }
}</code></pre><p>Once configured, all calls through the <code>Date</code> facade as well as Laravel helpers (<code>now()</code>, <code>today()</code>) return immutable instances:</p>
<pre><code class="language-php">$date = now();

$tomorrow = $date-&gt;addDay();

echo $date-&gt;toDateString();     // 2026-09-03 (original remains unchanged)
echo $tomorrow-&gt;toDateString(); // 2026-09-04</code></pre><hr>
<h2>2. The Direct Carbon Trap</h2>
<p>While <code>Date::use(CarbonImmutable::class)</code> secures <code>now()</code>, <code>today()</code>, and <code>Date::now()</code>, any code directly calling <code>Carbon::now()</code> or <code>Carbon::parse()</code> bypasses Laravel&#39;s date factory and remains mutable:</p>
<pre><code class="language-php">use Carbon\Carbon;

// ❌ Bypasses Laravel&#039;s Date factory; remains mutable
$date = Carbon::parse(&#039;2026-09-03&#039;);

// ✅ Routes through Date factory; returns CarbonImmutable
$date = Date::parse(&#039;2026-09-03&#039;);</code></pre><hr>
<h2>3. Automate Codebase Migration with Rector</h2>
<p>To migrate an existing codebase so all static Carbon calls route through Laravel&#39;s <code>Date</code> facade, use <a href="https://getrector.com/rule-detail/carbon-to-date-facade-rector"><code>CarbonToDateFacadeRector</code></a> from <code>rector-laravel</code>:</p>
<pre><code class="language-bash">composer require --dev driftingly/rector-laravel</code></pre><p>Register the rule in <code>rector.php</code>:</p>
<pre><code class="language-php">// rector.php
use Rector\Config\RectorConfig;
use RectorLaravel\Rector\StaticCall\CarbonToDateFacadeRector;

return RectorConfig::configure()
    -&gt;withRules([
        CarbonToDateFacadeRector::class,
    ]);</code></pre><h3>What Rector Automatically Transforms</h3>
<pre><code class="language-php">// Before Rector:
use Carbon\Carbon;

$now = Carbon::now();
$date = Carbon::parse(&#039;2026-01-01&#039;);

// After Rector:
use Illuminate\Support\Facades\Date;

$now = Date::now();
$date = Date::parse(&#039;2026-01-01&#039;);</code></pre><hr>
<h2>Key Benefits</h2>
<ul>
<li><strong>Predictable Date Math</strong>: Operations like <code>addDay()</code> or <code>subMonth()</code> always return new instances without mutating source dates.</li>
<li><strong>Framework Uniformity</strong>: Using <code>Date::use(CarbonImmutable::class)</code> guarantees all Eloquent timestamps, cast dates, and helpers share the same immutable behavior.</li>
<li><strong>Automated Modernization</strong>: <code>CarbonToDateFacadeRector</code> removes manual search-and-replace across legacy controllers and services.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Utilities</category>
            <category>Laravel</category>
            <category>Carbon</category>
            <category>Dates</category>
            <category>Rector</category>
            <category>Refactoring</category>
        </item>
        <item>
            <title><![CDATA[Harden Filesystem Operations with Storage::path() Traversal Protection in Laravel 13.30]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-storage-path-traversal-protection</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-storage-path-traversal-protection</guid>
            <pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Laravel 13.30 automatically normalizes and validates paths in Storage::path(), throwing a PathTraversalDetected exception if a path attempts to escape the configured disk root.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Laravel 13.30 automatically normalizes and validates paths in Storage::path(), throwing a PathTraversalDetected exception if a path attempts to escape the configured disk root.</p>
</blockquote>
<p>When generating absolute filesystem paths using <code>Storage::path($filePath)</code>, Laravel resolves the argument relative to the disk root configured in <code>config/filesystems.php</code> (such as <code>storage/app/private</code>).</p>
<p>If your application accepts file paths from user input without strict sanitization, malicious users can inject directory traversal sequences (<code>../../../</code>) to access sensitive configuration files, environment variables, or operating system secrets.</p>
<p><strong>Laravel 13.30 hardens <code>Storage::path()</code> against path traversal attacks</strong>, rejecting any resolved path that escapes the boundary of the configured storage disk.</p>
<hr>
<h2>1. The Vulnerability: Path Traversal via User Input</h2>
<p>User-controlled file paths can enter your application through multiple entry points:</p>
<ul>
<li>Query parameters (<code>?file=reports/summary.pdf</code>)</li>
<li>Route parameters (<code>/downloads/{filePath}</code>)</li>
<li>Form request fields (<code>request()-&gt;input(&#39;attachment&#39;)</code>)</li>
<li>JSON API payloads (<code>{ &quot;export_path&quot;: &quot;exports/data.csv&quot; }</code>)</li>
</ul>
<blockquote>
<p><strong>Warning for Laravel Versions Prior to 13.30</strong>:<br>In versions before Laravel 13.30, <code>Storage::path()</code> does not validate path boundaries. Passing unvalidated user input containing <code>../</code> sequences will silently resolve files outside your disk root. If you are on an older version, you must manually sanitize input using <code>basename()</code> or verify that the resolved <code>realpath()</code> starts within the intended storage directory before reading or serving files.</p>
</blockquote>
<hr>
<h2>2. Laravel 13.30: Automatic Path Boundary Enforcement</h2>
<p>In Laravel 13.30, <code>Storage::path()</code> normalizes dot segments (<code>.</code> and <code>..</code>) and validates that the final target path stays strictly inside the configured disk root. If the path tries to break out, Laravel immediately throws <code>PathTraversalDetected</code>.</p>
<pre><code class="language-php">use Illuminate\Filesystem\PathTraversalDetected;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

// Malicious payload from query string: ?file=../../../../.env
$requestedFile = $request-&gt;query(&#039;file&#039;);

try {
    // Attempting to resolve a path outside storage/app/private
    $fullPath = Storage::disk(&#039;local&#039;)-&gt;path($requestedFile);
} catch (PathTraversalDetected $e) {
    // Laravel 13.30 halts execution immediately
    abort(400, &#039;Invalid file path requested.&#039;);
}</code></pre><hr>
<h2>3. Defense in Depth: Framework Boundary vs. Application Security</h2>
<p>While Laravel 13.30 provides essential framework-level protection against filesystem escaping, it is not a substitute for application-level authorization.</p>
<p>Security requires two distinct layers:</p>
<h3>Framework Responsibility</h3>
<ul>
<li>Enforces hard storage boundaries.</li>
<li>Rejects any path traversal attempt that escapes the configured storage disk root with <code>PathTraversalDetected</code>.</li>
</ul>
<h3>Application Responsibility</h3>
<ul>
<li><strong>Input Validation</strong>: Never pass raw user-supplied strings directly to filesystem APIs without validating against an allowed list or strict pattern.</li>
<li><strong>Access Authorization</strong>: Verify that the authenticated user owns or has permission to view the requested resource (e.g. via Gates or Policies).</li>
<li><strong>ID-Based Lookups</strong>: Prefer database IDs or UUIDs to locate files instead of exposing raw filesystem paths to the client.</li>
</ul>
<hr>
<h2>4. Best Practice for Serving Private Files</h2>
<p>Instead of accepting raw path strings from user input, store file metadata in your database and resolve files using secure model relationships:</p>
<pre><code class="language-php">use App\Models\Invoice;
use Illuminate\Support\Facades\Gate;

public function download(Invoice $invoice)
{
    // 1. Authorize user access
    Gate::authorize(&#039;view&#039;, $invoice);

    // 2. Resolve verified path stored internally in the database
    return Storage::disk(&#039;local&#039;)-&gt;download($invoice-&gt;storage_path);
}</code></pre><hr>
<h2>Summary</h2>
<ul>
<li><code>Storage::path()</code> in Laravel 13.30 normalizes path strings and prevents traversal beyond the disk root.</li>
<li>Throws <code>Illuminate\Filesystem\PathTraversalDetected</code> when a path attempts to escape the storage root.</li>
<li>If running Laravel versions prior to 13.30, be vigilant and manually validate paths with <code>basename()</code> or <code>realpath()</code> checks.</li>
<li>Protects against malicious query parameters, route arguments, and JSON payloads.</li>
<li>Serves as defense in depth alongside proper input validation and user authorization.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Storage</category>
            <category>Laravel</category>
            <category>Storage</category>
            <category>Filesystem</category>
            <category>Security</category>
            <category>Path Traversal</category>
        </item>
        <item>
            <title><![CDATA[Git Submodule vs. Git Subtree: Which Should You Use?]]></title>
            <link>https://mrpunyapal.dev/blog/git-submodule-vs-git-subtree</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/git-submodule-vs-git-subtree</guid>
            <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A practical comparison of Git Submodules and Git Subtrees to help you choose the right strategy for embedding one repository inside another.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>A practical comparison of Git Submodules and Git Subtrees to help you choose the right strategy for embedding one repository inside another.</p>
</blockquote>
<p>When managing projects that share common libraries, documentation sets, microservices, or reusable UI components, you frequently need to embed one Git repository inside another.</p>
<p>Git provides two distinct mechanisms to handle this:</p>
<ul>
<li><strong>Git Submodules</strong>: Keep the external repository strictly independent; the parent repository only stores a pointer to a specific commit SHA.</li>
<li><strong>Git Subtrees</strong>: Merge the external repository files and commit history directly into a subdirectory of the parent repository.</li>
</ul>
<hr>
<h2>1. Git Submodules: Independent Pointer</h2>
<p>With submodules, the external project remains an entirely separate repository. The parent repository does not store the files directly; it only tracks the submodule URL and a commit hash in <code>.gitmodules</code>.</p>
<pre><code class="language-text">Main Repository
├── app/
├── config/
└── packages/shared-lib/  → [Pointer to Commit abc1234 in external repo]</code></pre><h3>Adding a Submodule</h3>
<pre><code class="language-bash"># Add external repo into packages/shared-lib
git submodule add https://github.com/example/shared-lib.git packages/shared-lib

# Stage and commit the .gitmodules and submodule reference
git commit -m &quot;Add shared-lib submodule&quot;</code></pre><h3>Cloning a Repository with Submodules</h3>
<p>When team members clone a repository containing submodules, standard <code>git clone</code> leaves submodule directories empty. They must pass the <code>--recurse-submodules</code> flag:</p>
<pre><code class="language-bash"># Clone parent and initialize all nested submodules
git clone --recurse-submodules https://github.com/example/main-app.git

# Or initialize submodules in an existing clone
git submodule update --init --recursive</code></pre><h3>Updating a Submodule</h3>
<p>To pull the latest changes from the submodule remote branch and update the parent commit pointer:</p>
<pre><code class="language-bash">git submodule update --remote packages/shared-lib
git add packages/shared-lib
git commit -m &quot;Update shared-lib to latest commit&quot;</code></pre><hr>
<h2>2. Git Subtrees: In-Tree Merged History</h2>
<p>With subtrees, the external repository&#39;s files are committed directly into your repository. Collaborators can clone, pull, and edit files normally without needing extra Git flags or submodule commands.</p>
<pre><code class="language-text">Main Repository
├── app/
├── config/
└── packages/shared-lib/  → [Real files + merged commit history]</code></pre><h3>Adding a Subtree</h3>
<pre><code class="language-bash"># 1. Add the external repository as a remote
git remote add shared-lib https://github.com/example/shared-lib.git

# 2. Add the subtree into packages/shared-lib using --squash
git subtree add --prefix=packages/shared-lib shared-lib main --squash</code></pre><h3>Pulling Upstream Updates</h3>
<p>When the external repository publishes new commits, pull them into the parent project:</p>
<pre><code class="language-bash">git subtree pull --prefix=packages/shared-lib shared-lib main --squash</code></pre><h3>Pushing Local Changes Upstream</h3>
<p>If you make modifications inside <code>packages/shared-lib</code> within the parent repo and want to push those commits back to the original repository:</p>
<pre><code class="language-bash">git subtree push --prefix=packages/shared-lib shared-lib main</code></pre><hr>
<h2>3. Side-by-Side Comparison</h2>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Git Submodule</th>
<th>Git Subtree</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Storage in Parent Repo</strong></td>
<td>Only commit SHA reference (<code>.gitmodules</code>)</td>
<td>Full file contents and history</td>
</tr>
<tr>
<td><strong>Cloning Experience</strong></td>
<td>Requires <code>--recurse-submodules</code></td>
<td>Normal <code>git clone</code> (no extra flags)</td>
</tr>
<tr>
<td><strong>Developer Complexity</strong></td>
<td>Higher (requires submodule workflow knowledge)</td>
<td>Lower (files behave like normal code)</td>
</tr>
<tr>
<td><strong>History Isolation</strong></td>
<td>Completely separate Git trees</td>
<td>Merged into the parent repository tree</td>
</tr>
<tr>
<td><strong>Locking Exact Versions</strong></td>
<td>Strict (pinned to exact commit hash)</td>
<td>Flexible (updated on demand via pull)</td>
</tr>
<tr>
<td><strong>Pushing Changes Back</strong></td>
<td>Direct commits inside submodule directory</td>
<td>Requires <code>git subtree push</code> command</td>
</tr>
<tr>
<td><strong>Third-Party Dependencies</strong></td>
<td>Ideal for external code you rarely modify</td>
<td>Ideal for shared code you actively edit</td>
</tr>
</tbody></table>
<hr>
<h2>4. Which One Should You Choose?</h2>
<h3>Choose Git Submodules if:</h3>
<ul>
<li>The embedded repository has its own strict release cycle and version tags.</li>
<li>You want the parent repository to lock onto an exact commit hash.</li>
<li>Team members and CI pipelines are comfortable initializing submodules (<code>--recurse-submodules</code>).</li>
<li>The embedded project is large and you want to keep repository clone sizes small.</li>
</ul>
<h3>Choose Git Subtrees if:</h3>
<ul>
<li>You want a friction-free clone experience for team members and contributors.</li>
<li>You want the external code to behave like ordinary files inside the repository.</li>
<li>You frequently edit the embedded files directly alongside your application code.</li>
<li>You want to avoid detached HEAD states and submodule initialization errors in CI.</li>
</ul>
<hr>
<h2>5. Common Gotchas</h2>
<h3>Submodule Gotchas:</h3>
<ul>
<li><strong>Empty Folders on Clone</strong>: Forgetting <code>--recurse-submodules</code> leaves submodule folders blank.</li>
<li><strong>Uncommitted Pointer Updates</strong>: Making changes inside the submodule folder without committing the updated reference in the parent repository causes inconsistent states between team members.</li>
<li><strong>Detached HEAD</strong>: By default, submodules check out detached commits rather than tracking branches.</li>
</ul>
<h3>Subtree Gotchas:</h3>
<ul>
<li><strong>Longer Commands</strong>: Subtree syntax (<code>--prefix</code>, <code>--squash</code>, remote names) requires precise terminal commands.</li>
<li><strong>Repo Bloat</strong>: Importing large repositories with long commit histories increases the size of your parent repository.</li>
</ul>
<hr>
<h2>Summary</h2>
<ul>
<li>Use <strong>Git Submodules</strong> when you need strict commit pinning and want the dependency to remain an isolated external project.</li>
<li>Use <strong>Git Subtrees</strong> when you want shared code to live directly in your repository with direct cloning for collaborators.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Git</category>
            <category>Workflow</category>
            <category>Git</category>
            <category>Version Control</category>
            <category>Workflow</category>
            <category>DevOps</category>
            <category>Monorepo</category>
        </item>
        <item>
            <title><![CDATA[Simplify Collection Operations with Higher-Order Messages]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-collection-higher-order-messages</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-collection-higher-order-messages</guid>
            <pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Laravel Collections support higher-order messages through dynamic properties, allowing operations like each, map, filter, and groupBy to work directly with an item's property or method without writing boilerplate callbacks.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Laravel Collections support higher-order messages through dynamic properties, allowing operations like each, map, filter, and groupBy to work directly with an item&#39;s property or method without writing boilerplate callbacks.</p>
</blockquote>
<p>When manipulating collections in Laravel, developers frequently pass anonymous functions that do nothing more than read a single object property or invoke a single method.</p>
<p>Writing boilerplate closures for simple property forwarding adds unnecessary visual noise.</p>
<p>Laravel provides <strong>higher-order messages</strong> via proxy properties on collections, allowing you to access item attributes and methods directly on the collection itself.</p>
<hr>
<h2>The Core Concept: Removing Boilerplate Callbacks</h2>
<p>Consider grouping a collection of orders by their status:</p>
<h3>Before</h3>
<pre><code class="language-php">$orders-&gt;groupBy(function (Order $order) {
    return $order-&gt;status;
});</code></pre><h3>After</h3>
<pre><code class="language-php">$orders-&gt;groupBy-&gt;status;</code></pre><p>The closure existed only to access the <code>status</code> property. With higher-order messages, Laravel intercepts the property access and applies it to each item in the collection automatically.</p>
<hr>
<h2>How Higher-Order Messages Work</h2>
<p>Higher-order messages are accessed as dynamic properties on the Collection instance (<code>$collection-&gt;methodName-&gt;target</code>).</p>
<p>Behind the scenes, Laravel returns a <code>HigherOrderCollectionProxy</code> instance. When you access a property or call a method on this proxy, Laravel applies that operation to each item during the collection loop:</p>
<pre><code class="language-text">$collection-&gt;each-&gt;markAsVip()
         ↓
HigherOrderCollectionProxy
         ↓
Loops items and invokes $item-&gt;markAsVip()</code></pre><hr>
<h2>Property Access vs. Method Calls</h2>
<p>Depending on the collection method and what each item exposes, higher-order messages can forward either a <strong>property access</strong> or a <strong>method invocation</strong>:</p>
<h3>1. Reading an Attribute (Property Access)</h3>
<p>When accessing model attributes or array keys:</p>
<pre><code class="language-php">// Sum the total property across all orders
$totalRevenue = $orders-&gt;sum-&gt;total;

// Group items by category property
$groupedProducts = $products-&gt;groupBy-&gt;category;

// Sort users by created_at timestamp descending
$recentUsers = $users-&gt;sortByDesc-&gt;created_at;</code></pre><h3>2. Invoking an Action (Method Call)</h3>
<p>When running a method on each item in the collection:</p>
<pre><code class="language-php">// Execute markAsVip() on each User instance
$users-&gt;each-&gt;markAsVip();

// Filter users where isActive() returns true
$activeUsers = $users-&gt;filter-&gt;isActive();

// Reject expired subscriptions
$validSubscriptions = $subscriptions-&gt;reject-&gt;isExpired();</code></pre><hr>
<h2>Real-World Practical Examples</h2>
<pre><code class="language-php">namespace App\Services;

use App\Models\Invoice;
use App\Models\User;
use Illuminate\Support\Collection;

class BillingService
{
    /**
     * @param Collection&lt;int, User&gt; $users
     */
    public function processVipMembers(Collection $users): void
    {
        // 1. Filter active users
        $activeVips = $users-&gt;filter-&gt;isVip();

        // 2. Perform an action on each
        $activeVips-&gt;each-&gt;sendVipNewsletter();
    }

    /**
     * @param Collection&lt;int, Invoice&gt; $invoices
     */
    public function summarizeInvoices(Collection $invoices): array
    {
        return [
            // Group invoices by payment status
            &#039;by_status&#039;     =&gt; $invoices-&gt;groupBy-&gt;status,
            // Calculate total unpaid balance
            &#039;unpaid_total&#039;  =&gt; $invoices-&gt;filter-&gt;isUnpaid()-&gt;sum-&gt;amount,
            // Find highest single invoice
            &#039;max_invoice&#039;   =&gt; $invoices-&gt;max-&gt;amount,
            // Extract unique customer IDs
            &#039;customer_ids&#039;  =&gt; $invoices-&gt;unique-&gt;customer_id-&gt;map-&gt;customer_id,
        ];
    }
}</code></pre><hr>
<h2>Complete Supported Method Reference</h2>
<p>Higher-order messages are supported across 23 Collection methods, organized by category:</p>
<pre><code class="language-php">
// Aggregation: calculate values from a property or method.
$collection-&gt;average-&gt;property;
$collection-&gt;avg-&gt;property;
$collection-&gt;max-&gt;property;
$collection-&gt;min-&gt;property;
$collection-&gt;sum-&gt;property;

// Iteration: call methods or transform items without callbacks.
$collection-&gt;each-&gt;method();
$collection-&gt;map-&gt;property;
$collection-&gt;flatMap-&gt;method();

// Filtering: keep, remove, or check items using a property or method.
$collection-&gt;contains-&gt;method();
$collection-&gt;every-&gt;method();
$collection-&gt;filter-&gt;method();
$collection-&gt;reject-&gt;method();
$collection-&gt;some-&gt;method();

// Finding &amp; grouping: locate, group, partition, or key items.
$collection-&gt;first-&gt;method();
$collection-&gt;groupBy-&gt;property;
$collection-&gt;keyBy-&gt;property;
$collection-&gt;partition-&gt;method();
$collection-&gt;unique-&gt;property;

// Sorting: order items using a property or method.
$collection-&gt;sortBy-&gt;property;
$collection-&gt;sortByDesc-&gt;property;

// Skipping &amp; taking: include or skip items based on conditions.
$collection-&gt;skipUntil-&gt;method();
$collection-&gt;skipWhile-&gt;method();
$collection-&gt;takeUntil-&gt;method();
$collection-&gt;takeWhile-&gt;method();</code></pre><hr>
<h2>When to Keep the Closure</h2>
<p>Higher-order messages are designed for direct property reads and single method calls. If your callback involves custom logic, comparisons, or multi-step transformations, continue using standard closures:</p>
<pre><code class="language-php">// Keep a closure when custom business logic or conditionals are needed:
$orders-&gt;groupBy(function (Order $order) {
    return $order-&gt;total &gt; 1000 ? &#039;tier_enterprise&#039; : &#039;tier_standard&#039;;
});

// Keep a closure when multiple arguments or external variables are required:
$users-&gt;each(function (User $user) use ($notifier, $discountCode) {
    $notifier-&gt;sendCustomPromotion($user, $discountCode);
});</code></pre><p>The general rule: <strong>Use higher-order messages when a callback only forwards a property or method; keep a closure when you need logic.</strong></p>
<hr>
<h2>Summary</h2>
<ul>
<li>Higher-order messages eliminate boilerplate closures across 23 Laravel Collection methods.</li>
<li>Accessed via dynamic properties on the collection (<code>$collection-&gt;filter-&gt;isActive()</code>).</li>
<li>Supports both property retrieval (<code>$orders-&gt;sum-&gt;total</code>) and method execution (<code>$users-&gt;each-&gt;delete()</code>).</li>
<li>Keeps chained collection pipelines readable and concise without sacrificing clarity.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Collections</category>
            <category>Laravel</category>
            <category>Collections</category>
            <category>Eloquent</category>
            <category>PHP</category>
            <category>Clean Code</category>
        </item>
        <item>
            <title><![CDATA[Cache Playwright and Puppeteer Browsers in GitHub Actions]]></title>
            <link>https://mrpunyapal.dev/blog/github-actions-cache-playwright-puppeteer</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/github-actions-cache-playwright-puppeteer</guid>
            <pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Cache browser binaries with actions/cache to avoid downloading 150MB+ on every CI run.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Cache browser binaries with <code>actions/cache</code> to avoid downloading 150MB+ on every CI run.</p>
</blockquote>
<p>Playwright and Puppeteer download full browser binaries (Chromium, Firefox, WebKit) during installation. Without caching, every workflow run downloads these binaries from scratch, adding 30-90 seconds of network time depending on the browser set and runner region.</p>
<h2>Cache Playwright Browsers</h2>
<pre><code class="language-yaml">jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22

      - run: npm ci

      - name: Cache Playwright browsers
        id: playwright-cache
        uses: actions/cache@v4
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ hashFiles(&#039;package-lock.json&#039;) }}

      - name: Install Playwright Chromium
        if: steps.playwright-cache.outputs.cache-hit != &#039;true&#039;
        run: npx playwright install chromium

      - name: Install Playwright system deps
        run: npx playwright install-deps chromium

      - name: Run browser tasks
        run: node scripts/generate-screenshots.js</code></pre><h3>How This Works</h3>
<ol>
<li><code>actions/cache</code> checks for a cached copy of <code>~/.cache/ms-playwright</code> matching the lock file hash.</li>
<li>On a cache hit, browser binaries are restored in ~5 seconds instead of downloaded in ~60 seconds.</li>
<li>The <code>install chromium</code> step is skipped entirely on cache hits.</li>
<li>System dependencies (<code>install-deps</code>) must run every time because they install OS-level packages (<code>libnss3</code>, <code>libatk1.0</code>, etc.) that are not persisted across runner instances.</li>
</ol>
<hr>
<h2>Cache Puppeteer Browsers</h2>
<p>Puppeteer stores browsers in a different default location. The pattern is the same with a different cache path:</p>
<pre><code class="language-yaml">      - name: Cache Puppeteer browsers
        id: puppeteer-cache
        uses: actions/cache@v4
        with:
          path: ~/.cache/puppeteer
          key: puppeteer-${{ runner.os }}-${{ hashFiles(&#039;package-lock.json&#039;) }}

      - name: Install Puppeteer browser
        if: steps.puppeteer-cache.outputs.cache-hit != &#039;true&#039;
        run: npx puppeteer browsers install chrome</code></pre><hr>
<h2>Cache Key Strategy</h2>
<p>The cache key <code>playwright-${{ runner.os }}-${{ hashFiles(&#39;package-lock.json&#39;) }}</code> ensures:</p>
<ul>
<li><strong>OS specificity</strong>: Browser binaries are platform-specific. A Linux-cached browser will not work on macOS or Windows runners.</li>
<li><strong>Version tracking</strong>: When <code>playwright</code> or <code>puppeteer</code> is upgraded in <code>package-lock.json</code>, the hash changes, and a fresh download is triggered with the correct browser version.</li>
</ul>
<p>For monorepos or workspaces with multiple lock files, point <code>hashFiles</code> to the relevant lock file:</p>
<pre><code class="language-yaml">key: playwright-${{ runner.os }}-${{ hashFiles(&#039;apps/web/package-lock.json&#039;) }}</code></pre><hr>
<h2>Install Only What You Need</h2>
<p>Both Playwright and Puppeteer support installing individual browsers. If your workflow only needs Chromium, skip the rest:</p>
<pre><code class="language-bash"># Playwright: install only Chromium (skip Firefox, WebKit)
npx playwright install chromium

# Puppeteer: install only Chrome
npx puppeteer browsers install chrome</code></pre><p>Installing all browsers downloads 400MB+ and triples the cache size. Install only what your workflow actually uses.</p>
<hr>
<h2>Combining Cache with Conditional Steps</h2>
<p>If some workflow runs do not need a browser at all, combine caching with conditional execution to skip both the cache restore and the install step:</p>
<pre><code class="language-yaml">      - name: Detect if screenshots needed
        id: changes
        run: |
          if git diff --name-only HEAD~1 -- &#039;src/content/**&#039; | grep -q .; then
            echo &quot;needs_browser=true&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
          fi

      - name: Cache Playwright browsers
        if: steps.changes.outputs.needs_browser == &#039;true&#039;
        id: playwright-cache
        uses: actions/cache@v4
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ hashFiles(&#039;package-lock.json&#039;) }}

      - name: Install Playwright Chromium
        if: steps.changes.outputs.needs_browser == &#039;true&#039; &amp;&amp; steps.playwright-cache.outputs.cache-hit != &#039;true&#039;
        run: npx playwright install chromium</code></pre><p>This avoids restoring a 150MB cache on runs that will never use a browser.</p>
<hr>
<h2>Key Points</h2>
<ul>
<li>Browser binaries are often the largest single download in Node.js CI workflows.</li>
<li>Cache <code>~/.cache/ms-playwright</code> (Playwright) or <code>~/.cache/puppeteer</code> (Puppeteer) using <code>actions/cache</code>.</li>
<li>Include <code>package-lock.json</code> hash in the cache key so version upgrades trigger a fresh download.</li>
<li>System dependencies (<code>install-deps</code>) must run on every workflow run, even on cache hits.</li>
<li>Install only the specific browsers your workflow uses to minimize download and cache size.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Git</category>
            <category>GitHub Actions</category>
            <category>Git</category>
            <category>GitHub Actions</category>
            <category>CI/CD</category>
            <category>Playwright</category>
            <category>Puppeteer</category>
        </item>
        <item>
            <title><![CDATA[Write Files Incrementally in Build Scripts to Avoid Unnecessary Rebuilds]]></title>
            <link>https://mrpunyapal.dev/blog/github-actions-incremental-file-writing</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/github-actions-incremental-file-writing</guid>
            <pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Compare generated content against existing files before writing. This prevents unnecessary git diffs, cache invalidation, and downstream rebuilds.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Compare generated content against existing files before writing. This prevents unnecessary git diffs, cache invalidation, and downstream rebuilds.</p>
</blockquote>
<p>Build scripts that generate HTML, JSON, XML, or other output files often write the result unconditionally, even when the content has not changed. This creates problems in CI pipelines: unchanged files get new timestamps, <code>git status</code> reports false modifications, auto-commit steps push empty diffs, and downstream caches are invalidated for no reason.</p>
<h2>The Problem</h2>
<p>A typical build script writes output directly:</p>
<pre><code class="language-javascript">import fs from &#039;node:fs&#039;;

const html = generatePage(data);
fs.writeFileSync(&#039;dist/index.html&#039;, html);</code></pre><p>If <code>generatePage</code> produces the same HTML as the file already on disk, the write is wasted. Worse, it updates the file&#39;s modification timestamp, which can trigger:</p>
<ul>
<li><code>git diff</code> reporting the file as changed (if line endings or encoding differ)</li>
<li>Downstream tools rebuilding dependents of that file</li>
<li>CI auto-commit steps creating empty or noise-only commits</li>
<li>CDN or deployment cache invalidation for files that did not actually change</li>
</ul>
<hr>
<h2>The Fix: Compare Before Writing</h2>
<p>Read the existing file, compare it with the new content, and skip the write when they match:</p>
<pre><code class="language-javascript">import fs from &#039;node:fs&#039;;
import path from &#039;node:path&#039;;

function writeIfChanged(filePath, content) {
  try {
    const existing = fs.readFileSync(filePath, &#039;utf-8&#039;);
    if (existing === content) return false;
  } catch {
    // File does not exist yet, proceed with write
  }

  fs.mkdirSync(path.dirname(filePath), { recursive: true });
  fs.writeFileSync(filePath, content);
  return true;
}</code></pre><p>Usage in a build script:</p>
<pre><code class="language-javascript">const pages = generateAllPages(data);
let written = 0;

for (const [filePath, html] of Object.entries(pages)) {
  if (writeIfChanged(filePath, html)) {
    written++;
  }
}

console.log(`Wrote ${written}/${Object.keys(pages).length} files (rest unchanged)`);</code></pre><p>On a typical run where 3 out of 300 pages changed, you will see:</p>
<pre><code class="language-text">Wrote 3/300 files (rest unchanged)</code></pre><hr>
<h2>Apply to Every Generated Output</h2>
<p>This pattern works for any generated file type:</p>
<pre><code class="language-javascript">// HTML pages
writeIfChanged(&#039;dist/tips.html&#039;, renderTipsPage(tips));

// JSON search indexes
writeIfChanged(&#039;public/search-index.json&#039;, JSON.stringify(index));

// XML feeds and sitemaps
writeIfChanged(&#039;public/feed.xml&#039;, renderRssFeed(posts));
writeIfChanged(&#039;public/sitemap.xml&#039;, renderSitemap(routes));</code></pre><hr>
<h2>CI Auto-Commit: Only Commit When Files Actually Changed</h2>
<p>When your workflow auto-commits generated files, the incremental write pattern prevents empty commits:</p>
<pre><code class="language-yaml">- name: Generate static assets
  run: node scripts/build.js

- name: Commit generated files if changed
  run: |
    git add -A
    if git diff --cached --quiet; then
      echo &quot;No changes to commit&quot;
    else
      git commit -m &quot;chore: regenerate static assets&quot;
      git push
    fi</code></pre><p>Without incremental writes, <code>git diff --cached</code> would always detect changes because every file received a fresh write (and potentially a new timestamp or trailing whitespace difference), leading to commits that contain no meaningful content changes.</p>
<hr>
<h2>Key Points</h2>
<ul>
<li>Always compare new content with existing files before writing in build scripts.</li>
<li>Skipping unchanged writes prevents false <code>git diff</code> results, unnecessary cache invalidation, and empty auto-commits.</li>
<li>The <code>try/catch</code> pattern handles missing files gracefully on first builds.</li>
<li>This optimization compounds in projects with hundreds of generated files, where only a few change per commit.</li>
<li>Pair with <code>git diff --cached --quiet</code> in CI to avoid committing when nothing actually changed.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Git</category>
            <category>GitHub Actions</category>
            <category>Git</category>
            <category>GitHub Actions</category>
            <category>CI/CD</category>
            <category>Build Tools</category>
        </item>
        <item>
            <title><![CDATA[Skip Expensive CI Steps Using git diff Instead of File Timestamps]]></title>
            <link>https://mrpunyapal.dev/blog/github-actions-skip-steps-with-git-diff</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/github-actions-skip-steps-with-git-diff</guid>
            <pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[File modification timestamps are unreliable in GitHub Actions. Use git diff to detect actual changes and conditionally skip expensive workflow steps.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>File modification timestamps are unreliable in GitHub Actions. Use <code>git diff</code> to detect actual changes and conditionally skip expensive workflow steps.</p>
</blockquote>
<p>Many build tools use file modification timestamps (<code>mtime</code>) to decide what needs rebuilding. This works on local machines because the filesystem tracks when each file was last saved.</p>
<p>In GitHub Actions, <code>actions/checkout</code> resets <strong>every file&#39;s timestamp</strong> to the moment of checkout. A project with 500 files will have all 500 stamped with the exact same time, regardless of when each was actually last modified.</p>
<p>Any script comparing timestamps to decide &quot;has this source changed since the last build?&quot; will answer &quot;yes&quot; for everything, triggering full rebuilds on every single run.</p>
<h2>The Problem in Practice</h2>
<p>Consider a build script that skips PDF generation when the output is newer than the source:</p>
<pre><code class="language-javascript">import fs from &#039;node:fs&#039;;

const srcStat = fs.statSync(&#039;template.html&#039;);
const outStat = fs.statSync(&#039;output.pdf&#039;);

if (outStat.mtimeMs &gt; srcStat.mtimeMs) {
  console.log(&#039;PDF is up to date, skipping&#039;);
  process.exit(0);
}

// ... expensive PDF generation with Puppeteer</code></pre><p>Locally, this works perfectly. In CI, both files receive the checkout timestamp, so <code>outStat.mtimeMs</code> equals <code>srcStat.mtimeMs</code>, and the skip condition fails every time.</p>
<hr>
<h2>The Fix: Use git diff</h2>
<p>Git tracks actual content changes regardless of filesystem timestamps. Replace <code>mtime</code> checks with <code>git diff</code>:</p>
<pre><code class="language-yaml"># .github/workflows/build.yml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2  # Need parent commit for diff

      - name: Detect changed files
        id: changes
        run: |
          if git diff --name-only HEAD~1 -- &#039;src/resume/**&#039; &#039;templates/resume.html&#039; | grep -q .; then
            echo &quot;resume_changed=true&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
          fi
          if git diff --name-only HEAD~1 -- &#039;src/content/**&#039; | grep -q .; then
            echo &quot;content_changed=true&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
          fi

      - name: Generate PDF Resume
        if: steps.changes.outputs.resume_changed == &#039;true&#039;
        run: node scripts/generate-pdf.js

      - name: Generate Screenshots
        if: steps.changes.outputs.content_changed == &#039;true&#039;
        run: node scripts/generate-screenshots.js</code></pre><h3>Why fetch-depth: 2?</h3>
<p>By default, <code>actions/checkout</code> performs a shallow clone with <code>fetch-depth: 1</code>, which only contains the latest commit. <code>git diff HEAD~1</code> requires the parent commit to exist. Setting <code>fetch-depth: 2</code> fetches exactly enough history for a single-commit diff.</p>
<hr>
<h2>Handling repository_dispatch and workflow_dispatch</h2>
<p>When workflows are triggered by <code>repository_dispatch</code> or <code>workflow_dispatch</code>, there is no new commit to diff against. Handle these triggers explicitly:</p>
<pre><code class="language-yaml">- name: Detect changed files
  id: changes
  run: |
    EVENT=&quot;${{ github.event_name }}&quot;

    # Dispatch events have no associated commit diff
    if [[ &quot;$EVENT&quot; == &quot;repository_dispatch&quot; || &quot;$EVENT&quot; == &quot;workflow_dispatch&quot; ]]; then
      echo &quot;content_changed=true&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
      exit 0
    fi

    if git diff --name-only HEAD~1 -- &#039;src/content/**&#039; | grep -q .; then
      echo &quot;content_changed=true&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;
    fi</code></pre><p>For <code>repository_dispatch</code>, you often know which subsystem triggered the event based on the <code>event_type</code>. Use that to set only the relevant flags instead of enabling everything.</p>
<hr>
<h2>The Same Pattern Inside Build Scripts</h2>
<p>You can also move the <code>git diff</code> check directly into your build scripts:</p>
<pre><code class="language-javascript">import { execSync } from &#039;node:child_process&#039;;

function hasSourceChanged(paths) {
  try {
    const diff = execSync(
      `git diff --name-only HEAD~1 -- ${paths.join(&#039; &#039;)}`,
      { encoding: &#039;utf-8&#039; }
    ).trim();
    return diff.length &gt; 0;
  } catch {
    // If git diff fails (shallow clone, initial commit), assume changed
    return true;
  }
}

if (!hasSourceChanged([&#039;template.html&#039;, &#039;styles/resume.css&#039;])) {
  console.log(&#039;No source changes detected, skipping generation&#039;);
  process.exit(0);
}

// ... proceed with expensive work</code></pre><p>The <code>try/catch</code> fallback is important. On initial commits, force pushes, or misconfigured shallow clones, <code>git diff HEAD~1</code> will fail. Defaulting to &quot;changed&quot; ensures the build still runs when change detection is unavailable.</p>
<hr>
<h2>Key Points</h2>
<ul>
<li><code>actions/checkout</code> sets all file timestamps to the checkout time, making <code>mtime</code>-based incremental logic unreliable in CI.</li>
<li>Use <code>git diff --name-only HEAD~1</code> to detect files that actually changed in the latest commit.</li>
<li>Set <code>fetch-depth: 2</code> in <code>actions/checkout</code> so the parent commit is available for diffing.</li>
<li>Handle <code>repository_dispatch</code> and <code>workflow_dispatch</code> triggers separately, since they have no associated commit diff.</li>
<li>Always fall back to &quot;assume changed&quot; when <code>git diff</code> fails, so builds are never silently skipped due to detection errors.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Git</category>
            <category>GitHub Actions</category>
            <category>Git</category>
            <category>GitHub Actions</category>
            <category>CI/CD</category>
            <category>Performance</category>
        </item>
        <item>
            <title><![CDATA[Query AI Embeddings with MariaDB Vector Distance in Laravel 13.27]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-mariadb-vector-distance-queries</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-mariadb-vector-distance-queries</guid>
            <pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Laravel 13.27 adds native vector distance query support for MariaDB 11.7+, enabling semantic search and similarity filtering directly through the query builder.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Laravel 13.27 adds native vector distance query support for MariaDB 11.7+, enabling semantic search and similarity filtering directly through the query builder.</p>
</blockquote>
<p>When building AI-powered features (such as semantic document search, recommendation engines, or RAG context retrieval), text is converted into high-dimensional vector embeddings and stored in your database.</p>
<p>Previously, running vector similarity searches in MariaDB required writing database-specific raw SQL statements with <code>VEC_DISTANCE_EUCLIDEAN()</code> or <code>VEC_DISTANCE_COSINE()</code>.</p>
<p><strong>Laravel 13.27 brings native vector query methods to MariaDB</strong>, allowing you to filter, sort, and calculate vector distances directly through Eloquent and the query builder.</p>
<hr>
<h2>The Query Builder Workflow</h2>
<pre><code class="language-php">use App\Models\Document;

// User search query converted to float array embeddings
$queryEmbedding = [0.0124, -0.0432, 0.0891, ...];

$documents = Document::query()
    // Keep only sufficiently close vectors
    -&gt;whereVectorDistanceLessThan(&#039;embedding&#039;, $queryEmbedding, maxDistance: 0.5)
    // Order results from closest to farthest
    -&gt;orderByVectorDistance(&#039;embedding&#039;, $queryEmbedding)
    // Include the computed distance value in results
    -&gt;selectVectorDistance(&#039;embedding&#039;, $queryEmbedding, as: &#039;distance&#039;)
    -&gt;limit(10)
    -&gt;get();</code></pre><hr>
<h2>Vector Query Methods Explained</h2>
<h3>1. <code>whereVectorDistanceLessThan()</code></h3>
<p>Filters rows where the distance between the stored column vector and the query vector is below a specified threshold:</p>
<pre><code class="language-php">// Only match documents within 0.4 distance
$query-&gt;whereVectorDistanceLessThan(&#039;embedding&#039;, $queryEmbedding, maxDistance: 0.4);</code></pre><h3>2. <code>orderByVectorDistance()</code></h3>
<p>Sorts results by proximity to the query embedding. By default, it sorts in ascending order (<code>asc</code>) so the most relevant/closest vectors appear first:</p>
<pre><code class="language-php">// Closest records first
$query-&gt;orderByVectorDistance(&#039;embedding&#039;, $queryEmbedding, &#039;asc&#039;);</code></pre><h3>3. <code>selectVectorDistance()</code></h3>
<p>Calculates the exact distance score and attaches it as a virtual attribute on the returned model instance:</p>
<pre><code class="language-php">$document = Document::query()
    -&gt;selectVectorDistance(&#039;embedding&#039;, $queryEmbedding, as: &#039;score&#039;)
    -&gt;first();

echo $document-&gt;score; // e.g. 0.1428</code></pre><hr>
<h2>Practical Scenario: Semantic Knowledge Base Search</h2>
<pre><code class="language-text">User Search: &quot;How do I configure Redis queues?&quot;
     ↓
Embedding API (e.g. OpenAI / Ollama)
     ↓
Float Array: [0.012, -0.045, ...]
     ↓
MariaDB Vector Query (VEC_DISTANCE)
     ↓
Top 5 Relevant Help Articles</code></pre><pre><code class="language-php">namespace App\Http\Controllers;

use App\Models\Article;
use App\Services\EmbeddingService;
use Illuminate\Http\Request;

class ArticleSearchController extends Controller
{
    public function __invoke(Request $request, EmbeddingService $embeddings)
    {
        $queryVector = $embeddings-&gt;generate($request-&gt;input(&#039;q&#039;));

        $articles = Article::query()
            -&gt;whereVectorDistanceLessThan(&#039;embedding&#039;, $queryVector, maxDistance: 0.45)
            -&gt;orderByVectorDistance(&#039;embedding&#039;, $queryVector)
            -&gt;selectVectorDistance(&#039;embedding&#039;, $queryVector, as: &#039;relevance_distance&#039;)
            -&gt;limit(5)
            -&gt;get();

        return response()-&gt;json($articles);
    }
}</code></pre><p>The database executes the mathematical distance calculations internally using its native vector engine, returning only matching rows to PHP.</p>
<hr>
<h2>Database Requirements</h2>
<ul>
<li><strong>MariaDB Version</strong>: Requires <strong>MariaDB 11.7+</strong>, which introduces native <code>VECTOR</code> data types and vector distance functions.</li>
<li><strong>Column Definition</strong>: The target column in your migration should be configured to store vector data:<pre><code class="language-php">Schema::create(&#039;documents&#039;, function (Blueprint $table) {
    $table-&gt;id();
    $table-&gt;string(&#039;title&#039;);
    $table-&gt;text(&#039;content&#039;);
    $table-&gt;vector(&#039;embedding&#039;, dimensions: 1536);
    $table-&gt;timestamps();
});</code></pre></li>
</ul>
<pre><code>
---

## Summary

- Laravel 13.27 brings first-class vector query builder methods to MariaDB 11.7+.
- `whereVectorDistanceLessThan()` filters rows based on distance thresholds.
- `orderByVectorDistance()` sorts records by nearest similarity.
- `selectVectorDistance()` selects the computed score as a model attribute.
- Replaces raw `VEC_DISTANCE_*` SQL clauses with clean, fluent Eloquent methods.</code></pre>]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Database</category>
            <category>Laravel</category>
            <category>MariaDB</category>
            <category>Database</category>
            <category>AI</category>
            <category>Vector Search</category>
        </item>
        <item>
            <title><![CDATA[Monitor Aggregate Queue Metrics with Total Size Helpers in Laravel 13.27]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-queue-total-size-metrics</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-queue-total-size-metrics</guid>
            <pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Laravel 13.27 adds totalPendingSize(), totalDelayedSize(), and totalReservedSize() to the Queue facade, making it easy to aggregate queue metrics for dashboards and health checks.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Laravel 13.27 adds totalPendingSize(), totalDelayedSize(), and totalReservedSize() to the Queue facade, making it easy to aggregate queue metrics for dashboards and health checks.</p>
</blockquote>
<p>When building admin panels, operational dashboards, or custom health-check endpoints, tracking queue volume across your application is a common requirement.</p>
<p>Prior to Laravel 13.27, <code>Queue::size($queue)</code> only reported pending counts for a single named queue. Getting total metrics across multiple queues required manually querying each individual queue and summing the counts in application code.</p>
<p><strong>Laravel 13.27 introduces dedicated aggregate size helpers</strong> directly on the <code>Queue</code> facade.</p>
<hr>
<h2>1. Laravel 13.27 Total Size Helpers</h2>
<pre><code class="language-php">use Illuminate\Support\Facades\Queue;

$pending = Queue::totalPendingSize();
// Jobs waiting to be processed.

$delayed = Queue::totalDelayedSize();
// Jobs intentionally delayed for later.

$reserved = Queue::totalReservedSize();
// Jobs currently reserved by workers.</code></pre><hr>
<h2>2. Understanding Each Queue Metric</h2>
<ul>
<li><strong><code>totalPendingSize()</code></strong>: The total count of ready jobs waiting in queues to be claimed by worker processes. A sustained spike indicates worker starvation or backlog accumulation.</li>
<li><strong><code>totalDelayedSize()</code></strong>: The total count of jobs scheduled with <code>delay()</code> that are waiting for their release timestamp to expire before becoming pending.</li>
<li><strong><code>totalReservedSize()</code></strong>: The total count of jobs currently claimed and actively being executed by worker processes.</li>
</ul>
<hr>
<h2>3. Practical Example: Lightweight Dashboard &amp; Health Endpoints</h2>
<p>These helpers make it simple to assemble lightweight queue health payloads without setting up heavy external monitoring packages:</p>
<pre><code class="language-php">namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Queue;

class QueueMetricsController extends Controller
{
    public function __invoke(): JsonResponse
    {
        $pending  = Queue::totalPendingSize();
        $delayed  = Queue::totalDelayedSize();
        $reserved = Queue::totalReservedSize();

        return response()-&gt;json([
            &#039;status&#039;   =&gt; $pending &gt; 5000 ? &#039;degraded&#039; : &#039;healthy&#039;,
            &#039;metrics&#039;  =&gt; [
                &#039;pending&#039;   =&gt; $pending,
                &#039;delayed&#039;   =&gt; $delayed,
                &#039;reserved&#039;  =&gt; $reserved,
                &#039;total&#039;     =&gt; $pending + $delayed + $reserved,
            ],
            &#039;timestamp&#039; =&gt; now()-&gt;toIso8601String(),
        ]);
    }
}</code></pre><hr>
<h2>4. Querying Specific Connections</h2>
<p>By default, the helpers aggregate metrics across the default queue connection. You can target specific connections when running multiple queue backends (like separate Redis and database queues):</p>
<pre><code class="language-php">use Illuminate\Support\Facades\Queue;

// Query a dedicated Redis queue connection
$redisPending = Queue::connection(&#039;redis&#039;)-&gt;totalPendingSize();

// Query a database fallback connection
$dbDelayed = Queue::connection(&#039;database&#039;)-&gt;totalDelayedSize();</code></pre><p><em>(Note: These helpers provide fast aggregate totals for lightweight reporting. For detailed per-job telemetry, throughput graphs, and runtime tags, use Laravel Horizon or Laravel Pulse.)</em></p>
<hr>
<h2>Summary</h2>
<ul>
<li><code>Queue::totalPendingSize()</code> returns all jobs waiting to be processed.</li>
<li><code>Queue::totalDelayedSize()</code> returns all delayed jobs awaiting release.</li>
<li><code>Queue::totalReservedSize()</code> returns all jobs actively reserved by workers.</li>
<li>Avoids manual multi-queue loop-and-sum aggregation.</li>
<li>Supports chaining <code>connection(&#39;name&#39;)</code> for multi-backend setups.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Queue</category>
            <category>Laravel</category>
            <category>Queue</category>
            <category>Monitoring</category>
            <category>Architecture</category>
            <category>DevOps</category>
        </item>
        <item>
            <title><![CDATA[Apply Individual Commits Across Branches with Git cherry-pick]]></title>
            <link>https://mrpunyapal.dev/blog/git-cherry-pick-single-commit</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/git-cherry-pick-single-commit</guid>
            <pubDate>Sat, 29 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Use git cherry-pick to apply specific commits from another branch to your current branch without merging the entire branch history.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Use git cherry-pick to apply specific commits from another branch to your current branch without merging the entire branch history.</p>
</blockquote>
<p>When working across multiple feature branches, you often write a useful bug fix, documentation tweak, or isolated utility method on a feature branch that is still in development.</p>
<p>Merging the entire feature branch into <code>main</code> would pull in unfinished work and untested features.</p>
<p>Instead of manually copy-pasting the changes, <strong><code>git cherry-pick</code></strong> allows you to select a specific commit from any branch and apply its diff directly onto your current branch.</p>
<hr>
<h2>The Basic Workflow</h2>
<pre><code class="language-bash"># 1. Switch to the target branch that should receive the change
git checkout main

# 2. Apply the specific commit by its hash
git cherry-pick 4ecc3a3

# 3. Push the newly created commit to the remote repository
git push origin main</code></pre><p><em>(Note: <code>4ecc3a3</code> is an example commit hash. Replace it with your actual commit SHA from <code>git log</code>.)</em></p>
<hr>
<h2>What Actually Happens?</h2>
<pre><code class="language-text">feature-branch
    ├── commit A (WIP UI refactor)
    ├── commit B (Bug fix for auth redirect)  ← Need this on main
    └── commit C (WIP checkout page)

git checkout main
git cherry-pick &lt;commit-B-hash&gt;

main
    └── commit B&#039; (New commit with identical diff, new SHA)</code></pre><p>When you run <code>git cherry-pick 4ecc3a3</code>:</p>
<ol>
<li>Git extracts the exact patch (diff) introduced by commit <code>4ecc3a3</code>.</li>
<li>It applies those changes onto the working tree of your currently checked-out branch.</li>
<li>It creates a <strong>new commit</strong> with a new commit hash and timestamp on your current branch.</li>
</ol>
<p>The original commit on the source branch remains untouched.</p>
<hr>
<h2>Cherry-Pick vs. Merge</h2>
<table>
<thead>
<tr>
<th>Operation</th>
<th>Command</th>
<th>What It Does</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Merge</strong></td>
<td><code>git merge feature</code></td>
<td>Combines two full branch histories together, including all commits and ancestors.</td>
</tr>
<tr>
<td><strong>Cherry-Pick</strong></td>
<td><code>git cherry-pick &lt;hash&gt;</code></td>
<td>Copies only the specific changes from selected commits into new commits on the active branch.</td>
</tr>
</tbody></table>
<p>Use <code>git merge</code> when an entire feature is finished and ready for integration. Use <code>git cherry-pick</code> when you need an isolated fix without bringing over the surrounding branch work.</p>
<hr>
<h2>Handling Conflicts</h2>
<p>If the files modified by the cherry-picked commit have diverged significantly on your target branch, Git will pause and flag a conflict.</p>
<h3>To resolve and continue:</h3>
<pre><code class="language-bash"># 1. Resolve conflict markers in your editor, then stage the resolved files
git add .

# 2. Continue the cherry-pick operation
git cherry-pick --continue</code></pre><h3>To cancel and return to clean state:</h3>
<pre><code class="language-bash"># Aborts the cherry-pick and returns your branch to its pre-cherry-pick state
git cherry-pick --abort</code></pre><hr>
<h2>Cherry-Picking Multiple Commits</h2>
<p>You can also cherry-pick multiple commits in a single operation:</p>
<pre><code class="language-bash"># Cherry-pick individual non-contiguous commits
git cherry-pick abc1234 def5678

# Cherry-pick a contiguous range of commits (from A up to B, exclusive of A)
git cherry-pick abc1234..def5678

# Cherry-pick a contiguous range including A (inclusive)
git cherry-pick abc1234^..def5678</code></pre><hr>
<h2>Practical Scenarios</h2>
<ul>
<li><strong>Hotfixes on Production Branches</strong>: A bug was resolved on <code>main</code> or a <code>develop</code> branch and needs to be backported immediately to a live <code>v1.x</code> release branch.</li>
<li><strong>Accidental Commits</strong>: You accidentally committed a change to the wrong branch. You can cherry-pick it onto the correct branch and reset the original branch.</li>
<li><strong>Extracted Refactors</strong>: While building a long-running feature, you wrote a clean helper method that teammates need immediately on <code>main</code>.</li>
</ul>
<hr>
<h2>Summary</h2>
<ul>
<li><code>git cherry-pick &lt;hash&gt;</code> applies the diff of a specific commit onto your current branch.</li>
<li>Creates a new commit with its own unique SHA on the active branch without altering the original commit.</li>
<li>Use <code>git cherry-pick --continue</code> after resolving merge conflicts, or <code>git cherry-pick --abort</code> to cancel.</li>
<li>Avoids merging unfinished feature branches when only an isolated change is needed.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Git</category>
            <category>Workflow</category>
            <category>Git</category>
            <category>Version Control</category>
            <category>Workflow</category>
            <category>CLI</category>
        </item>
        <item>
            <title><![CDATA[Ignore Local Files Without Modifying .gitignore Using .git/info/exclude]]></title>
            <link>https://mrpunyapal.dev/blog/git-local-ignore-info-exclude</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/git-local-ignore-info-exclude</guid>
            <pubDate>Sat, 22 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Use .git/info/exclude to ignore personal scratch files, local debug scripts, and notes without polluting your team's shared .gitignore file.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Use .git/info/exclude to ignore personal scratch files, local debug scripts, and notes without polluting your team&#39;s shared .gitignore file.</p>
</blockquote>
<p>When working on a shared repository, you often create temporary files for local testing: a quick scratchpad (<code>scratchpad.md</code>), a temporary debugging script (<code>debug_local.php</code>), or a folder of personal investigation notes (<code>/notes/</code>).</p>
<p>A common developer habit is adding those personal patterns to the project&#39;s root <code>.gitignore</code> file. However, <code>.gitignore</code> is committed and tracked in version control. Adding personal, one-off file patterns creates noisy pull request diffs and forces team members to adopt ignore rules they do not need.</p>
<p>Git provides a built-in repository-local ignore file: <code>.git/info/exclude</code>.</p>
<h2>Three Scopes of Git Ignore Rules</h2>
<p>Understanding where to place an ignore pattern depends on who needs the rule:</p>
<pre><code class="language-text">.gitignore
  ↓
Shared project rules (Committed and tracked by the entire team)

.git/info/exclude
  ↓
Personal rules for this clone only (Uncommitted, stored inside .git/)

Global Gitignore (~/.gitignore)
  ↓
Personal rules across all repositories on your machine (e.g. .DS_Store, .vscode/)</code></pre><h2>How to Use .git/info/exclude</h2>
<p>Every Git repository includes a <code>.git/info/exclude</code> file by default. Because it lives inside the <code>.git</code> directory, it is never committed or pushed to remote repositories.</p>
<p>Open the file in your preferred editor:</p>
<pre><code class="language-bash">code .git/info/exclude</code></pre><p>Add your personal patterns using standard <code>.gitignore</code> syntax:</p>
<pre><code class="language-text"># Personal scratchpad and experimental scripts
scratchpad.md
debug_local.php
experimental_*.php

# Local private notes
/personal-notes/

# Local custom test database dump
local_dump.sql</code></pre><p>After saving the file, running <code>git status</code> will no longer report matching files under &quot;Untracked files&quot;, and running <code>git add .</code> will not accidentally stage them.</p>
<h2>Important: .git/info/exclude Only Ignores Untracked Files</h2>
<p>Like <code>.gitignore</code>, adding a pattern to <code>.git/info/exclude</code> applies strictly to <strong>untracked files</strong>.</p>
<p>If a file is already tracked in Git history, adding it to <code>.git/info/exclude</code> will not prevent Git from tracking subsequent changes to it.</p>
<p>If you need to stop tracking a file that was previously committed while keeping your local copy on disk, you must remove it from the Git index:</p>
<pre><code class="language-bash">git rm --cached path/to/file.txt</code></pre><p><em>(Note: <code>git rm --cached</code> stages a deletion in Git history for the next commit, so only use it if the file should truly be removed from the shared repository for all contributors).</em></p>
<h2>Decision Guide: Which Ignore Scope to Use</h2>
<table>
<thead>
<tr>
<th>Situation</th>
<th>Recommended Scope</th>
<th>Location</th>
</tr>
</thead>
<tbody><tr>
<td>Project dependencies, build outputs, and shared environment templates (<code>node_modules/</code>, <code>vendor/</code>, <code>dist/</code>, <code>.env.example</code>)</td>
<td>Project <code>.gitignore</code></td>
<td><code>.gitignore</code> in repository root</td>
</tr>
<tr>
<td>Personal scratch files, debug scripts, or local test fixtures specific to this clone (<code>scratchpad.md</code>, <code>debug_test.php</code>, <code>/my-notes/</code>)</td>
<td>Local repository exclude</td>
<td><code>.git/info/exclude</code></td>
</tr>
<tr>
<td>Machine-wide artifacts and OS/IDE files across all projects on your laptop (<code>.DS_Store</code>, <code>Thumbs.db</code>, <code>.idea/</code>, <code>.vscode/</code>)</td>
<td>Global Gitignore</td>
<td><code>~/.gitignore</code> (configured via <code>git config --global core.excludesFile</code>)</td>
</tr>
</tbody></table>
<h2>Summary</h2>
<ul>
<li>Use <code>.gitignore</code> for shared project ignore rules that every team member needs.</li>
<li>Use <code>.git/info/exclude</code> to ignore personal scratch files and temporary scripts in a specific repository without modifying committed files.</li>
<li>Use a global Gitignore (<code>core.excludesFile</code>) for operating system and editor artifacts across all repositories on your machine.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Git</category>
            <category>Workflow</category>
            <category>Git</category>
            <category>DevOps</category>
            <category>Workflow</category>
            <category>Tooling</category>
        </item>
        <item>
            <title><![CDATA[Process Improvements in Laravel 13.26: Idle Timeouts and Testing Assertions]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-process-idle-timeout-fake-assertions</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-process-idle-timeout-fake-assertions</guid>
            <pubDate>Sat, 22 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Laravel 13.26 introduces ProcessIdleTimedOutException to distinguish idle processes from overall runtime timeouts, alongside new testing assertions for execution count and sequence order.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Laravel 13.26 introduces ProcessIdleTimedOutException to distinguish idle processes from overall runtime timeouts, alongside new testing assertions for execution count and sequence order.</p>
</blockquote>
<p>Running external shell processes and CLI commands in Laravel is handled by the <code>Process</code> facade. When managing long-running tasks such as deployment scripts, data exports, or media processing, diagnosing process failures and writing comprehensive automated tests are critical requirements.</p>
<p>Laravel 13.26 improves the <code>Process</code> facade in two core areas:</p>
<ol>
<li><strong>Granular Timeout Exceptions</strong>: Differentiating between silent/idle processes and processes that exceed total execution time.</li>
<li><strong>Enhanced Process Fakes</strong>: New test assertions for process execution count (<code>assertRanCount</code>), sequence ordering (<code>assertRanInOrder</code>), and recorded interaction inspection (<code>recorded()</code>).</li>
</ol>
<hr>
<h2>1. Overall Timeout vs. Idle Timeout</h2>
<p>When executing an external command, two distinct timeout conditions can occur:</p>
<ul>
<li><strong>Overall Timeout (<code>timeout</code>)</strong>: The maximum total duration the process is allowed to run from start to finish.</li>
<li><strong>Idle Timeout (<code>idleTimeout</code>)</strong>: The maximum allowed duration without the process emitting any standard output (<code>stdout</code> or <code>stderr</code>).</li>
</ul>
<p>Before Laravel 13.26, exceeding either limit threw the generic <code>Illuminate\Process\Exceptions\ProcessTimedOutException</code>. In Laravel 13.26, idle timeouts trigger a dedicated exception: <code>Illuminate\Process\Exceptions\ProcessIdleTimedOutException</code>.</p>
<h3>Differentiating Process Failures</h3>
<pre><code class="language-php">use Illuminate\Process\Exceptions\ProcessIdleTimedOutException;
use Illuminate\Process\Exceptions\ProcessTimedOutException;
use Illuminate\Support\Facades\Process;

try {
    $result = Process::timeout(600)      // Total runtime limit: 10 minutes
        -&gt;idleTimeout(30)               // Output inactivity limit: 30 seconds
        -&gt;run(&#039;./deploy-services.sh&#039;);

    if ($result-&gt;successful()) {
        logger()-&gt;info(&#039;Deployment completed successfully.&#039;);
    }
} catch (ProcessIdleTimedOutException $e) {
    // The process stopped producing output for 30 seconds (e.g. waiting on a prompt or stuck in deadlock)
    logger()-&gt;error(&#039;Process stalled with no output: &#039; . $e-&gt;getMessage());
} catch (ProcessTimedOutException $e) {
    // The process continued producing output but exceeded the 10-minute overall limit
    logger()-&gt;error(&#039;Process exceeded total execution budget: &#039; . $e-&gt;getMessage());
}</code></pre><p>This separation allows automated pipelines to take targeted recovery steps, such as terminating unresponsive processes immediately when idle while permitting active, progressing tasks to run up to their total timeout limit.</p>
<hr>
<h2>2. Process Fake Testing Assertions</h2>
<p>Laravel&#39;s <code>Process::fake()</code> allows tests to mock process execution without executing actual shell commands. Laravel 13.26 adds dedicated assertion methods to inspect execution count, ordering, and recorded instances.</p>
<h3>Asserting Execution Count with <code>assertRanCount()</code></h3>
<p><code>Process::assertRanCount()</code> verifies the exact number of commands invoked during a test:</p>
<pre><code class="language-php">use Illuminate\Support\Facades\Process;

test(&#039;deployment command executes expected subprocesses&#039;, function () {
    Process::fake([
        &#039;git pull&#039; =&gt; Process::result(&#039;Already up to date.&#039;),
        &#039;composer install --no-dev&#039; =&gt; Process::result(&#039;Installing dependencies...&#039;),
        &#039;php artisan migrate --force&#039; =&gt; Process::result(&#039;Migrated successfully.&#039;),
    ]);

    $this-&gt;artisan(&#039;app:deploy&#039;);

    // Asserts that exactly 3 processes were executed
    Process::assertRanCount(3);
});</code></pre><p>This prevents subtle bugs where an unexpected branching condition executes a command multiple times or skips an intended execution silently.</p>
<h3>Asserting Command Sequence with <code>assertRanInOrder()</code></h3>
<p>When external tasks depend on strict chronological execution (such as pulling latest source code before migrating database tables), <code>Process::assertRanInOrder()</code> verifies the exact sequence:</p>
<pre><code class="language-php">use Illuminate\Support\Facades\Process;

test(&#039;build pipeline executes steps in chronological sequence&#039;, function () {
    Process::fake();

    $this-&gt;artisan(&#039;app:build&#039;);

    // Asserts commands ran in this exact order
    Process::assertRanInOrder([
        &#039;npm ci&#039;,
        &#039;npm run build&#039;,
        &#039;php artisan config:cache&#039;,
        &#039;php artisan route:cache&#039;,
    ]);
});</code></pre><h3>Inspecting Recorded Results with <code>recorded()</code></h3>
<p>The <code>Process::recorded()</code> method returns a collection of all executed processes and their corresponding result objects. You can also pass a closure to filter and inspect specific process runs:</p>
<pre><code class="language-php">use Illuminate\Process\PendingProcess;
use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Support\Facades\Process;

test(&#039;inspects specific command payloads&#039;, function () {
    Process::fake();

    $this-&gt;artisan(&#039;reports:generate&#039;);

    // Filter recorded interactions
    $exportProcesses = Process::recorded(function (PendingProcess $process, ProcessResult $result) {
        return str_contains($process-&gt;command, &#039;mysqldump&#039;);
    });

    expect($exportProcesses)-&gt;toHaveCount(1);
});</code></pre><hr>
<h2>Summary</h2>
<ul>
<li><strong><code>ProcessIdleTimedOutException</code></strong>: Catch output inactivity separately from total runtime timeouts via <code>idleTimeout()</code>.</li>
<li><strong><code>Process::assertRanCount()</code></strong>: Assert the total number of process invocations in tests.</li>
<li><strong><code>Process::assertRanInOrder()</code></strong>: Verify strict chronological command execution order.</li>
<li><strong><code>Process::recorded()</code></strong>: Inspect and filter recorded process and result pairs using closures.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Utilities</category>
            <category>Laravel</category>
            <category>Process</category>
            <category>Testing</category>
            <category>CLI</category>
            <category>Architecture</category>
        </item>
        <item>
            <title><![CDATA[Laravel 13.25 Reverses Merge Order in Request::all()]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-request-all-input-file-precedence</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-request-all-input-file-precedence</guid>
            <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In Laravel 13.25, Request::all() now gives submitted input precedence over uploaded files when their keys collide, while file() continues to provide direct access to uploaded files.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>In Laravel 13.25, Request::all() now gives submitted input precedence over uploaded files when their keys collide, while file() continues to provide direct access to uploaded files.</p>
</blockquote>
<p>When handling incoming HTTP requests containing both structured form fields and uploaded files, Laravel combines input data and file uploads into a unified collection when calling <code>$request-&gt;all()</code>.</p>
<p>Laravel 13.25 updates the internal merge order inside <code>Request::all()</code> to ensure submitted input values take precedence whenever an input field and an uploaded file share the exact same key.</p>
<h2>What Changed?</h2>
<p>Under the hood, Laravel combines the input bag (<code>$this-&gt;input()</code>) and the uploaded files bag (<code>$this-&gt;allFiles()</code>).</p>
<p>Prior to Laravel 13.25, the merge order placed files second:</p>
<pre><code class="language-php">// Before Laravel 13.25
array_replace_recursive($this-&gt;input(), $this-&gt;allFiles());</code></pre><p>In Laravel 13.25, this order is reversed so input is merged last:</p>
<pre><code class="language-php">// Laravel 13.25+
array_replace_recursive($this-&gt;allFiles(), $this-&gt;input());</code></pre><p>This ensures submitted input values win collisions across <code>$request-&gt;all()</code>, <code>$request-&gt;only()</code>, dynamic property access (<code>$request-&gt;profile</code>), and array access (<code>$request[&#39;profile&#39;]</code>).</p>
<h2>Example: Colliding Input and Upload Keys</h2>
<p>Consider a request containing a nested <code>profile.name</code> text field alongside an uploaded file also named <code>profile.name</code>:</p>
<pre><code class="language-php">use Illuminate\Http\Request;

$request = Request::create(&#039;/&#039;, &#039;POST&#039;, [
    &#039;profile&#039; =&gt; [
        &#039;name&#039; =&gt; &#039;Punyapal&#039;,
    ],
], [], [
    &#039;profile&#039; =&gt; [
        &#039;name&#039; =&gt; $file,
        &#039;avatar&#039; =&gt; $avatar,
    ],
]);

// Before Laravel 13.25
// Files were merged last, so a colliding file replaced the input.
$request-&gt;all();
// profile.name =&gt; UploadedFile

// Laravel 13.25
// Input is merged last, so the submitted value takes precedence.
$request-&gt;all();
// profile.name =&gt; &#039;Punyapal&#039;
// profile.avatar =&gt; UploadedFile

// Need the uploaded file? file() still returns it.
$request-&gt;file(&#039;profile.name&#039;);
// UploadedFile</code></pre><h2>Uploaded Files Remain Accessible via file()</h2>
<p>This change only alters key collisions inside <code>$request-&gt;all()</code> and related helper methods. Laravel does not discard the uploaded file.</p>
<p>When you need the uploaded file instance, the dedicated <code>$request-&gt;file()</code> API is completely unaffected and continues to return the <code>UploadedFile</code> object directly:</p>
<pre><code class="language-php">$avatar = $request-&gt;file(&#039;profile.name&#039;);</code></pre><h2>Nested Recursive Merging</h2>
<p>Because Laravel uses <code>array_replace_recursive()</code>, precedence only applies to specific keys that collide:</p>
<ul>
<li><code>profile.name</code> exists in both input and files, so the submitted string <code>&#39;Punyapal&#39;</code> takes precedence in <code>all()</code>.</li>
<li><code>profile.avatar</code> exists only in the files bag, so it continues to appear in <code>$request-&gt;all()</code> as an <code>UploadedFile</code> without interference.</li>
</ul>
<h2>Summary</h2>
<p><code>Request::all()</code> now prefers submitted input values over files when key collisions occur, while <code>$request-&gt;file()</code> remains the dedicated way to retrieve uploaded files.</p>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>HTTP &amp; API</category>
            <category>Laravel</category>
            <category>HTTP</category>
            <category>Request</category>
            <category>Files</category>
        </item>
        <item>
            <title><![CDATA[Build Polished Static Documentation Sites from Markdown with DocSmith]]></title>
            <link>https://mrpunyapal.dev/blog/php-docsmith-static-documentation-generator</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/php-docsmith-static-documentation-generator</guid>
            <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[DocSmith is a lightweight PHP tool that compiles Markdown directories into responsive, searchable, SEO-friendly static documentation websites with zero frontend configuration.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>DocSmith is a lightweight PHP tool that compiles Markdown directories into responsive, searchable, SEO-friendly static documentation websites with zero frontend configuration.</p>
</blockquote>
<p>Writing technical documentation in Markdown is straightforward, but transforming those files into a searchable, responsive website often introduces complex JavaScript static site generators, node build pipelines, or heavy CMS platforms.</p>
<p><code>mrpunyapal/docsmith</code> is a native PHP library and standalone CLI tool that compiles a directory of Markdown files directly into clean static HTML pages with built-in dark mode, dual-theme syntax highlighting, search, and AI-ready exports.</p>
<p>You can explore a live example of a site generated with DocSmith at <a href="https://mrpunyapal.github.io/docsmith/">mrpunyapal.github.io/docsmith</a>.</p>
<h2>How DocSmith Works</h2>
<p>DocSmith follows a straightforward static compilation pipeline:</p>
<pre><code class="language-text">Markdown Files (docs/)
          ↓
DocSmith Engine
├── Frontmatter parsing and document discovery
├── League CommonMark (GFM) + Phiki syntax highlighting
├── Navigation tree generation and custom ordering
└── Search index, sitemap, and LLM text generation
          ↓
Static HTML Documentation Site (dist/)</code></pre><ol>
<li><strong>Source Discovery</strong>: Scans your documentation directory and parses YAML frontmatter metadata.</li>
<li><strong>Markdown Rendering</strong>: Converts Markdown through League CommonMark with GitHub-flavored Markdown extensions.</li>
<li><strong>Syntax Highlighting</strong>: Renders server-side code syntax highlighting using Phiki with GitHub Light and Dark themes.</li>
<li><strong>Site Assembly</strong>: Injects navigation, table of contents, search overlays, and responsive mobile drawers.</li>
<li><strong>Asset &amp; Metadata Publishing</strong>: Emits static assets, <code>search-index.json</code>, <code>sitemap.xml</code>, <code>llms.txt</code>, and <code>.nojekyll</code>.</li>
</ol>
<h2>Installation and Quick Start</h2>
<p>DocSmith requires <strong>PHP 8.3 or newer</strong> and Composer:</p>
<pre><code class="language-bash">composer require --dev mrpunyapal/docsmith</code></pre><h3>1. Build via CLI</h3>
<p>DocSmith ships with a standalone binary at <code>vendor/bin/docsmith</code>:</p>
<pre><code class="language-bash">vendor/bin/docsmith build --source=md --output=docs --title=&quot;Package Docs&quot;</code></pre><h3>2. Build via PHP API</h3>
<p>You can also run builds directly within PHP build scripts using the static entry point or fluent builder:</p>
<pre><code class="language-php">use Docsmith\Docsmith;

Docsmith::make()
    -&gt;source(__DIR__ . &#039;/md&#039;)
    -&gt;output(__DIR__ . &#039;/dist&#039;)
    -&gt;title(&#039;Acme Cache Docs&#039;)
    -&gt;description(&#039;High-performance caching library for PHP.&#039;)
    -&gt;accentColor(&#039;#ff2d20&#039;)
    -&gt;accentColorDark(&#039;#ff6b61&#039;)
    -&gt;siteUrl(&#039;https://acme.github.io/cache&#039;)
    -&gt;repositoryUrl(&#039;https://github.com/acme/cache&#039;)
    -&gt;rightSidebar()
    -&gt;build();</code></pre><h2>Organizing Markdown Files</h2>
<p>DocSmith maps your Markdown files directly to clean static URLs:</p>
<pre><code class="language-text">md/
├── index.md                    →  docs/index.html
├── installation.md             →  docs/installation/index.html
├── configuration.md            →  docs/configuration/index.html
└── usage/
    ├── basics.md               →  docs/usage/basics/index.html
    └── advanced.md             →  docs/usage/advanced/index.html</code></pre><p>If your source directory does not contain an <code>index.md</code>, DocSmith automatically generates a clean landing page listing all available documentation sections.</p>
<h3>Frontmatter Options</h3>
<p>Each Markdown file can include YAML frontmatter to control page metadata and navigation:</p>
<pre><code class="language-markdown">---
title: Advanced Configuration
description: Fine-tune caching drivers, timeouts, and memory limits.
sidebar_label: Configuration
order: 3
hidden: false
---

# Advanced Configuration</code></pre><ul>
<li><code>title</code>: Page title displayed in browser tabs and navigation.</li>
<li><code>description</code>: Meta description used for SEO and search snippets.</li>
<li><code>sidebar_label</code>: Shorter label displayed in the sidebar menu.</li>
<li><code>order</code>: Numeric sort priority.</li>
<li><code>hidden</code>: When set to <code>true</code>, the page is rendered to HTML but omitted from the sidebar, search index, and previous/next pagination.</li>
</ul>
<h2>Customizing Sidebar Navigation Order</h2>
<p>By default, pages appear in alphabetical or frontmatter-defined order. You can set an explicit sequence using <code>navigationOrder()</code>:</p>
<pre><code class="language-php">Docsmith::make()
    -&gt;source(__DIR__ . &#039;/md&#039;)
    -&gt;output(__DIR__ . &#039;/dist&#039;)
    -&gt;navigationOrder([
        &#039;Introduction&#039;,
        &#039;Installation&#039;,
        &#039;Configuration&#039;,
        &#039;Basic Usage&#039;,
        &#039;Advanced Usage&#039;,
    ])
    -&gt;build();</code></pre><p>Entries can match page titles, <code>sidebar_label</code> properties, or relative Markdown paths. Unlisted pages retain their natural position after ordered items.</p>
<h2>Polished Reading Experience</h2>
<p>DocSmith generates standalone, zero-dependency HTML with built-in UX features:</p>
<h3>1. Dual-Theme Syntax Highlighting</h3>
<p>Code blocks are highlighted during static compilation using <strong>Phiki</strong>, emitting token styles for both light and dark backgrounds without client-side highlighting libraries:</p>
<pre><code class="language-php">use Acme\Cache\Repository;

$cache = new Repository(driver: &#039;redis&#039;);
$cache-&gt;set(&#039;user:42&#039;, $user, ttl: 3600);</code></pre><p>Each code block includes a 1-click clipboard copy button.</p>
<h3>2. Light and Dark Theme Modes</h3>
<p>The site includes a theme toggle that automatically detects system color scheme preferences (<code>prefers-color-scheme</code>) and persists manual user selections via <code>localStorage</code>.</p>
<h3>3. Responsive Navigation &amp; Tables</h3>
<ul>
<li>Mobile layouts include a sliding navigation drawer with touch backdrop support.</li>
<li>Wide Markdown tables are automatically wrapped in <code>&lt;div class=&quot;table-scroll&quot;&gt;</code> containers to prevent viewport overflow on smaller screens.</li>
<li>An optional right sidebar (<code>-&gt;rightSidebar()</code>) extracts page headings into an active Table of Contents.</li>
</ul>
<h2>Built-In Search &amp; Modal Overlay</h2>
<p>DocSmith builds a static <code>search-index.json</code> during compilation containing page titles, descriptions, section headings, and content text:</p>
<ul>
<li><strong>Sidebar Filter</strong>: Instantly filters sidebar navigation links as you type.</li>
<li><strong>Global Search Overlay</strong>: Press <code>Cmd+K</code> (macOS) or <code>Ctrl+K</code> (Windows/Linux) or click the search bar to launch a modal search dialog with full content matching.</li>
</ul>
<h2>Multi-Version Documentation</h2>
<p>For libraries maintaining multiple major releases, DocSmith provides multi-version builds with a header version switcher:</p>
<pre><code class="language-php">Docsmith::make()
    -&gt;source(__DIR__ . &#039;/md&#039;)
    -&gt;output(__DIR__ . &#039;/dist&#039;)
    -&gt;versions([
        &#039;v1&#039; =&gt; [&#039;label&#039; =&gt; &#039;v1.x&#039;, &#039;source&#039; =&gt; __DIR__ . &#039;/md/v1&#039;, &#039;default&#039; =&gt; true],
        &#039;v2&#039; =&gt; [&#039;label&#039; =&gt; &#039;v2.x&#039;, &#039;source&#039; =&gt; __DIR__ . &#039;/md/v2&#039;],
    ])
    -&gt;build();</code></pre><ul>
<li>The default version outputs directly to the root (<code>/installation/index.html</code>).</li>
<li>Secondary versions output to versioned subpaths (<code>/v2/installation/index.html</code>).</li>
<li>A version switcher dropdown is automatically rendered in the header navigation.</li>
</ul>
<h2>AI-Ready Documentation Exports</h2>
<p>DocSmith automatically exports documentation in formats structured for LLMs, coding agents, and automated workflows:</p>
<table>
<thead>
<tr>
<th>File</th>
<th>Format and Purpose</th>
</tr>
</thead>
<tbody><tr>
<td><code>llms.txt</code></td>
<td>Standard directory index per the <a href="https://llmstxt.org/">llms.txt</a> specification with absolute URLs and summaries.</td>
</tr>
<tr>
<td><code>llms-full.txt</code></td>
<td>Concatenated plain-text export of the entire documentation site.</td>
</tr>
<tr>
<td><code>export/docs.md</code></td>
<td>Single unified Markdown file containing all pages and metadata.</td>
</tr>
</tbody></table>
<p>AI exports are enabled by default and can be toggled using <code>-&gt;llmsExport(false)</code>.</p>
<h2>Open Graph Social Cards</h2>
<p>DocSmith can generate Open Graph preview images using Playwright and capturist:</p>
<pre><code class="language-php">Docsmith::make()
    -&gt;source(__DIR__ . &#039;/md&#039;)
    -&gt;output(__DIR__ . &#039;/docs&#039;)
    -&gt;siteUrl(&#039;https://acme.github.io/cache&#039;)
    -&gt;ogGeneratedAll() // Generates docs/og/cover.png
    -&gt;build();</code></pre><p>You can generate a single shared social cover or individual cards per page (<code>-&gt;ogGeneratedPerPage()</code>), with support for custom HTML card templates (<code>-&gt;ogTemplate(...)</code>).</p>
<h2>Real-World Example: Package Documentation Script</h2>
<p>Here is a complete <code>build-docs.php</code> script for a PHP package:</p>
<pre><code class="language-php">&lt;?php

declare(strict_types=1);

require __DIR__ . &#039;/vendor/autoload.php&#039;;

use Docsmith\Docsmith;

Docsmith::make()
    -&gt;source(__DIR__ . &#039;/docs&#039;)
    -&gt;output(__DIR__ . &#039;/dist&#039;)
    -&gt;title(&#039;AuditLogger PHP&#039;)
    -&gt;description(&#039;Immutable audit logging and security tracing for PHP applications.&#039;)
    -&gt;accentColor(&#039;#0284c7&#039;)
    -&gt;accentColorDark(&#039;#38bdf8&#039;)
    -&gt;siteUrl(&#039;https://acme.github.io/audit-logger&#039;)
    -&gt;repositoryUrl(&#039;https://github.com/acme/audit-logger&#039;)
    -&gt;editBranch(&#039;main&#039;)
    -&gt;rightSidebar()
    -&gt;navigationOrder([
        &#039;Getting Started&#039;,
        &#039;Installation&#039;,
        &#039;Configuration&#039;,
        &#039;Logging Events&#039;,
        &#039;Custom Formatters&#039;,
    ])
    -&gt;build();

echo &quot;Documentation built successfully in dist/\n&quot;;</code></pre><p>Add a Composer shortcut to <code>composer.json</code>:</p>
<pre><code class="language-json">&quot;scripts&quot;: {
    &quot;docs:build&quot;: &quot;@php build-docs.php&quot;
}</code></pre><h2>Summary</h2>
<p>DocSmith provides a focused, native PHP solution for turning Markdown into production-ready static documentation sites:</p>
<ul>
<li><strong>Zero Node Runtime</strong>: Compiles complete HTML sites natively in PHP without requiring node modules or complex JavaScript build tools.</li>
<li><strong>Built-in Essentials</strong>: Includes dark mode, Phiki syntax highlighting, search index generation, mobile navigation, and LLM text exports out of the box.</li>
<li><strong>Hosting Agnostic</strong>: Generates clean static folders compatible with GitHub Pages (<code>.nojekyll</code>), Cloudflare Pages, Netlify, and Vercel.</li>
<li><strong>Live Documentation</strong>: View a live production example at <a href="https://mrpunyapal.github.io/docsmith/">mrpunyapal.github.io/docsmith</a>.</li>
<li><strong>GitHub Repository</strong>: <a href="https://github.com/MrPunyapal/docsmith">MrPunyapal/DocSmith</a></li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>PHP</category>
            <category>Tooling</category>
            <category>PHP</category>
            <category>DocSmith</category>
            <category>Documentation</category>
            <category>Tooling</category>
            <category>Open Source</category>
            <category>Markdown</category>
        </item>
        <item>
            <title><![CDATA[Terminal UI Upgrade for php artisan dev in Laravel 13.25]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-artisan-dev-multiplex-terminal-ui</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-artisan-dev-multiplex-terminal-ui</guid>
            <pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Laravel 13.25 upgrades php artisan dev with @laravel/multiplex to provide a tabbed terminal UI with process search, restarts, and log management.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Laravel 13.25 upgrades php artisan dev with @laravel/multiplex to provide a tabbed terminal UI with process search, restarts, and log management.</p>
</blockquote>
<p>Running <code>composer run dev</code> or <code>php artisan dev</code> manages multiple development processes in parallel, such as the PHP development server, queue workers, log streaming with Pail, and asset compilation with Vite.</p>
<p>Previously, all active processes wrote their output concurrently into a single shared scrolling terminal window, making it difficult to isolate specific server logs or track build errors.</p>
<p>Laravel 13.25 upgrades <code>php artisan dev</code> to use <code>@laravel/multiplex</code> as its terminal interface on supported platforms, giving each development process its own dedicated tab and interactive controls.</p>
<h2>What the Multiplex UI Provides</h2>
<p>The updated terminal interface organizes concurrent processes into distinct views and introduces several process management features:</p>
<ul>
<li><strong>Tabbed process views</strong>: Each registered command (HTTP server, Vite, Queue, Pail) runs in its own tab, selectable with keyboard shortcuts.</li>
<li><strong>Searchable output</strong>: Filter and search through process logs directly in the terminal interface.</li>
<li><strong>Individual process restarts</strong>: Restart a specific process (such as a queue worker) without restarting your entire development environment.</li>
<li><strong>Log clearing</strong>: Clear the log buffer for any individual process window.</li>
<li><strong>Automatic crash recovery</strong>: Processes that fail or crash can automatically restart in the background.</li>
<li><strong>Exit log preservation</strong>: When stopping <code>artisan dev</code>, buffered logs from each process are printed to the main terminal so recent errors are preserved.</li>
</ul>
<pre><code class="language-text">┌─────────────────────────────────────────────────────────┐
│  Laravel Dev                                            │
├──────────────┬──────────────────────────────────────────┤
│ ● Server     │                                          │
│ ● Queue      │   [active process output]                │
│ ● Pail       │                                          │
│ ● Vite       │   ...                                    │
├──────────────┴──────────────────────────────────────────┤
│ Search  │ Restart │ Clear │ ...                         │
└─────────────────────────────────────────────────────────┘</code></pre><h2>CLI Usage and Output Modes</h2>
<p>You can run <code>artisan dev</code> with flags to customize output formatting and runtime behavior:</p>
<pre><code class="language-bash"># Start all dev processes with tabbed Multiplex UI
composer run dev

# Or run directly via Artisan
php artisan dev

# Fall back to interleaved streaming output
php artisan dev --stream

# Prepend timestamps to log output lines
php artisan dev --timestamps

# Prevent crashed processes from automatically restarting
php artisan dev --no-restart</code></pre><p><code>php artisan dev</code> supports three distinct output modes:</p>
<ol>
<li><code>tabs</code> (default): Interactive tabbed interface powered by <code>@laravel/multiplex</code>.</li>
<li><code>stream</code>: Traditional interleaved terminal stream where all process logs appear in one combined feed.</li>
<li><code>inline</code>: Plain non-interactive output, activated automatically when running without an interactive TTY (such as in CI or scripted environments).</li>
</ol>
<h2>Configuring DevCommands in PHP</h2>
<p>In addition to CLI flags, you can configure default development process behavior globally using the <code>Illuminate\Foundation\DevCommands</code> facade:</p>
<pre><code class="language-php">use Illuminate\Foundation\DevCommands;

// Configure runtime options introduced in Laravel 13.25
DevCommands::stream();
DevCommands::withTimestamps();
DevCommands::disableAutoRestart();
DevCommands::bufferSize(5000);</code></pre><p>These configuration helpers complement existing process registration methods (such as <code>DevCommands::register()</code>, <code>artisan()</code>, <code>node()</code>, <code>only()</code>, and <code>except()</code>), allowing teams to enforce consistent local development defaults across an entire project.</p>
<h2>Platform and Node.js Requirements</h2>
<p>The <code>@laravel/multiplex</code> interface has specific platform prerequisites:</p>
<ul>
<li><strong>Supported Platforms</strong>: macOS and Linux.</li>
<li><strong>Windows Support</strong>: On Windows environments, <code>php artisan dev</code> automatically falls back to <code>concurrently</code> streaming.</li>
<li><strong>Node.js Version</strong>: The Multiplex path requires Node.js v22.13 or newer.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Utilities</category>
            <category>Laravel</category>
            <category>Artisan</category>
            <category>DX</category>
            <category>Tooling</category>
        </item>
        <item>
            <title><![CDATA[Use config() Instead of env() in Application Code]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-avoid-env-outside-config-pest-arch</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-avoid-env-outside-config-pest-arch</guid>
            <pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Call env() only inside configuration files and use config() throughout your application code to prevent missing values when configuration is cached.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Call env() only inside configuration files and use config() throughout your application code to prevent missing values when configuration is cached.</p>
</blockquote>
<p>A foundational Laravel convention is keeping <code>env()</code> calls confined strictly to files in the <code>config/</code> directory while reading settings across controllers, services, and models via <code>config()</code>.</p>
<h2>The Common Mistake</h2>
<p>It is tempting to read environment variables directly inside application code:</p>
<pre><code class="language-php">// ❌ Don&#039;t call env() directly in application code
$stripeKey = env(&#039;STRIPE_KEY&#039;);</code></pre><p>While this works during local development when <code>.env</code> is loaded on every request, it breaks in production when configuration caching is enabled.</p>
<h2>The Laravel Way</h2>
<p>Environment variables should be accessed inside configuration files, and application code should read from Laravel&#39;s configuration repository:</p>
<pre><code class="language-php">// config/services.php
return [
    &#039;stripe&#039; =&gt; [
        &#039;key&#039; =&gt; env(&#039;STRIPE_KEY&#039;),
    ],
];</code></pre><p>Application code then retrieves the setting through <code>config()</code>:</p>
<pre><code class="language-php">// ✅ Read the configured value
$stripeKey = config(&#039;services.stripe.key&#039;);</code></pre><p>The application does not need to know where the value originated; it accesses the value through Laravel&#39;s unified configuration repository.</p>
<h2>Why config:cache Matters</h2>
<p>In production, optimizing performance involves running:</p>
<pre><code class="language-bash">php artisan config:cache</code></pre><p>Once configuration is cached, Laravel combines all files in <code>config/*.php</code> into a single cached file and ignores the <code>.env</code> file during requests and Artisan commands.</p>
<p>When <code>.env</code> is not loaded:</p>
<ul>
<li><code>config(&#39;services.stripe.key&#39;)</code> reads the compiled value from the configuration cache.</li>
<li>Calling <code>env(&#39;STRIPE_KEY&#39;)</code> directly in application code bypasses the configuration cache and returns <code>null</code>, unless an external operating system environment variable of the same name is explicitly defined on the host server.</li>
</ul>
<h2>Enforce It with Pest</h2>
<p>You can enforce this convention automatically using a Pest architecture test:</p>
<pre><code class="language-php">// tests/ArchitectureTest.php
arch(&#039;globals&#039;)
    -&gt;expect([&#039;env&#039;])
    -&gt;not-&gt;toBeUsed();</code></pre><p>This test flags any direct <code>env()</code> usage in your application code before code is merged or deployed.</p>
<h2>Catch It with Larastan</h2>
<p>Larastan includes static analysis rules to detect <code>env()</code> usage outside configuration files.</p>
<p>The <code>larastan.noEnvCallsOutsideOfConfig</code> rule (enabled by default in Larastan 3.x) analyzes your codebase and reports violations:</p>
<pre><code class="language-text">Called &#039;env&#039; outside of the config directory which returns null when the config is cached, use &#039;config&#039;.</code></pre><h2>Quick Rule</h2>
<pre><code class="language-text">.env → env() → config/*.php → config() → application code</code></pre><ul>
<li><code>env()</code> belongs in <code>config/*.php</code> files.</li>
<li><code>config()</code> belongs everywhere else in application code.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Configuration</category>
            <category>Laravel</category>
            <category>Configuration</category>
            <category>Testing</category>
            <category>Pest PHP</category>
        </item>
        <item>
            <title><![CDATA[Process Uploaded Images from Streams in Laravel 13.25]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-image-stream-format-response</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-image-stream-format-response</guid>
            <pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Laravel 13.25 adds Image::fromStream(), makes toFormat() public, and pairs with toResponse() to process uploaded images and return them directly as HTTP responses.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Laravel 13.25 adds Image::fromStream(), makes toFormat() public, and pairs with toResponse() to process uploaded images and return them directly as HTTP responses.</p>
</blockquote>
<p>When handling user uploads, processing an image and returning a converted preview often involves reading files from streams rather than saving intermediate files to disk. Laravel 13.25 updates the Image API with stream support, making it straightforward to load, convert, and respond in a single flow.</p>
<h2>Process an Uploaded Image</h2>
<p>Here is a controller action that reads an uploaded image stream, converts it to WebP, and returns the converted image directly as an HTTP response:</p>
<pre><code class="language-php">namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Image;

class ImagePreviewController extends Controller
{
    public function preview(Request $request): Response
    {
        // Create the image directly from the uploaded file stream.
        $image = Image::fromStream(
            $request-&gt;file(&#039;image&#039;)-&gt;readStream()
        );

        // Convert the image to WebP.
        $image-&gt;toFormat(&#039;webp&#039;);

        // Return the processed image as an HTTP response.
        return $image-&gt;toResponse();
    }
}</code></pre><h2>What Each Method Does</h2>
<h3>Image::fromStream()</h3>
<p>Laravel 13.25 introduces <code>Image::fromStream()</code>, allowing you to instantiate an image directly from a readable stream resource such as <code>$request-&gt;file(&#39;image&#39;)-&gt;readStream()</code>:</p>
<pre><code class="language-php">$image = Image::fromStream(
    $request-&gt;file(&#039;image&#039;)-&gt;readStream()
);</code></pre><p>This reads the stream directly without requiring the uploaded file to be permanently stored on disk first.</p>
<h3>toFormat()</h3>
<p>In Laravel 13.25, <code>toFormat()</code> is now public on the image instance, allowing you to convert the in-memory image representation to a target format such as WebP, PNG, or JPEG:</p>
<pre><code class="language-php">$image-&gt;toFormat(&#039;webp&#039;);</code></pre><p>Calling <code>toFormat()</code> updates the image format in memory without writing it to storage.</p>
<h3>toResponse()</h3>
<p><code>toResponse()</code> converts the processed image into an <code>Illuminate\Http\Response</code> instance with the appropriate <code>Content-Type</code> header (such as <code>image/webp</code>):</p>
<pre><code class="language-php">return $image-&gt;toResponse();</code></pre><h2>The Flow</h2>
<pre><code class="language-text">Uploaded file
    ↓
fromStream()
    ↓
toFormat(&#039;webp&#039;)
    ↓
toResponse()</code></pre><p>This pattern is useful for on-the-fly preview endpoints, format conversion APIs, or serving dynamically formatted image responses directly from uploaded requests.</p>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>HTTP &amp; API</category>
            <category>Laravel</category>
            <category>Images</category>
            <category>HTTP</category>
        </item>
        <item>
            <title><![CDATA[Load Large SQL Dumps in Laravel Seeders]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-schema-state-load-seeding</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-schema-state-load-seeding</guid>
            <pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Use DB::getSchemaState()->load() to load large reference SQL dump files in seeders, with DB::unprepared(file_get_contents(...)) as a fallback for older Laravel versions.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Use DB::getSchemaState()-&gt;load() to load large reference SQL dump files in seeders, with DB::unprepared(file_get_contents(...)) as a fallback for older Laravel versions.</p>
</blockquote>
<p>When seeding large static reference datasets such as countries, states, cities, or timezones, writing thousands of individual Eloquent <code>create()</code> or <code>insert()</code> calls adds unnecessary overhead. Developers frequently store these datasets as raw <code>.sql</code> files inside their repository and load them during database seeding.</p>
<h2>The Common Approach</h2>
<p>A standard way to execute an external SQL file is reading its contents into PHP memory and executing the raw query buffer with <code>DB::unprepared()</code>:</p>
<pre><code class="language-php">use Illuminate\Support\Facades\DB;

DB::unprepared(
    file_get_contents(database_path(&#039;seeders/sql/world.sql&#039;))
);</code></pre><p>This approach works by having <code>file_get_contents()</code> read the entire SQL dump into a PHP string variable, which is then passed to PDO via <code>DB::unprepared()</code>. For very large SQL files, loading the entire file into PHP memory first can consume significant memory allocations.</p>
<h2>Prefer SchemaState When Available</h2>
<p>In Laravel versions that support schema state inspection, you can load the SQL file using <code>DB::getSchemaState()-&gt;load()</code>:</p>
<pre><code class="language-php">use Illuminate\Support\Facades\DB;

DB::getSchemaState()-&gt;load(
    database_path(&#039;seeders/sql/world.sql&#039;)
);</code></pre><p>Instead of requiring application code to read the complete SQL file into a PHP string in memory, <code>getSchemaState()-&gt;load()</code> delegates the file path directly to Laravel&#39;s database-specific schema state implementation.</p>
<h2>Older Laravel Projects</h2>
<p>For projects running on older Laravel versions where <code>DB::getSchemaState()-&gt;load()</code> is not available, <code>DB::unprepared(file_get_contents(...))</code> remains a reliable and practical solution. There is no need to restructure working seeders on older installations unless you encounter memory limits during data loading.</p>
<h2>Example Seeder</h2>
<p>Here is how a dedicated reference dataset seeder looks using the preferred approach:</p>
<pre><code class="language-php">namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class WorldSeeder extends Seeder
{
    public function run(): void
    {
        DB::getSchemaState()-&gt;load(
            database_path(&#039;seeders/sql/world.sql&#039;)
        );
    }
}</code></pre><p>If your project requires compatibility with older Laravel releases, the fallback pattern can be used inside the same seeder:</p>
<pre><code class="language-php">namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class WorldSeeder extends Seeder
{
    public function run(): void
    {
        DB::unprepared(
            file_get_contents(
                database_path(&#039;seeders/sql/world.sql&#039;)
            )
        );
    }
}</code></pre>]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Database</category>
            <category>Laravel</category>
            <category>Database</category>
            <category>Seeders</category>
        </item>
        <item>
            <title><![CDATA[Detect Eloquent Model Attribute Changes Before and After Saving]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-detect-model-changes-isdirty-waschanged</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-detect-model-changes-isdirty-waschanged</guid>
            <pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Use isDirty() and isClean() before saving to inspect unsaved in-memory mutations, and use wasChanged() and getOriginal() after saving to verify persisted changes and access previous values.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Use isDirty() and isClean() before saving to inspect unsaved in-memory mutations, and use wasChanged() and getOriginal() after saving to verify persisted changes and access previous values.</p>
</blockquote>
<p>When updating Eloquent models, tracking whether specific attributes have changed depends on whether the model has already been saved to the database. Laravel provides dedicated helper methods tailored for checking model state before and after saving.</p>
<h2>Inspecting Changes Before Saving</h2>
<p>Before calling <code>save()</code>, changes exist only in memory on the model instance.</p>
<ul>
<li><code>isDirty()</code>: Returns <code>true</code> if any attribute (or a specified attribute) has been modified since the model was loaded from the database.</li>
<li><code>isClean()</code>: The inverse of <code>isDirty()</code>. Returns <code>true</code> if an attribute remains unchanged.</li>
</ul>
<pre><code class="language-php">$user-&gt;name = &#039;Punyapal&#039;;

// Before saving: check current unsaved changes
$user-&gt;isDirty(&#039;name&#039;);  // true
$user-&gt;isClean(&#039;email&#039;); // true</code></pre><h2>Inspecting Changes After Saving</h2>
<p>Once <code>save()</code> executes, the model synchronizes its in-memory attributes with the database. At this point, calling <code>isDirty()</code> returns <code>false</code> because there are no longer any unsaved modifications.</p>
<p>To inspect what changed during the save operation, use <code>wasChanged()</code> and <code>getOriginal()</code>:</p>
<ul>
<li><code>wasChanged()</code>: Returns <code>true</code> if an attribute was updated during the most recent <code>save()</code> call.</li>
<li><code>getOriginal()</code>: Retrieves the attribute value as it was before mutations or before the save occurred.</li>
</ul>
<pre><code class="language-php">$user-&gt;save();

// After saving: check what changed during the last save
$user-&gt;wasChanged(&#039;name&#039;);  // true
$user-&gt;wasChanged(&#039;email&#039;); // false

// Retrieve the original value
$user-&gt;getOriginal(&#039;name&#039;); // previous name</code></pre><h2>Practical Example: Email Verification &amp; Notifications</h2>
<p>A common practical use case is resetting email verification when an email address changes and sending a verification notification after saving:</p>
<pre><code class="language-php">use App\Models\User;

$user = User::find(1);
$user-&gt;email = &#039;new-email@example.com&#039;;

// 1. Before saving: reset verification timestamp if email changed
if ($user-&gt;isDirty(&#039;email&#039;)) {
    $user-&gt;email_verified_at = null;
}

$user-&gt;save();

// 2. After saving: send verification notification only if email changed
if ($user-&gt;wasChanged(&#039;email&#039;)) {
    $user-&gt;sendEmailVerificationNotification();
}</code></pre><p>This pattern ensures that:</p>
<ol>
<li><code>isDirty(&#39;email&#39;)</code> modifies companion in-memory attributes so both columns are written in a single database <code>UPDATE</code> query.</li>
<li><code>wasChanged(&#39;email&#39;)</code> triggers external side effects (like sending emails) only after the changes have been successfully persisted.</li>
</ol>
<h2>Key Takeaways</h2>
<ul>
<li>Use <code>isDirty()</code> and <code>isClean()</code> prior to persistence to conditionally run validation or assign companion attributes.</li>
<li>Use <code>wasChanged()</code> inside model events, observers, or service classes after calling <code>save()</code> to dispatch notifications only when relevant fields change.</li>
<li>Pass attribute names as arguments (for example, <code>isDirty(&#39;email&#39;)</code> or <code>wasChanged(&#39;email&#39;)</code>) to check specific columns rather than the entire model.</li>
<li>Use <code>getOriginal(&#39;column&#39;)</code> when you need the previous value for audit logging or old-vs-new comparison.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Eloquent</category>
            <category>Laravel</category>
            <category>Eloquent</category>
            <category>Database</category>
        </item>
        <item>
            <title><![CDATA[Prevent Navigation Layout Shifts and Flickering in Web Applications]]></title>
            <link>https://mrpunyapal.dev/blog/css-prevent-navigation-layout-shift-flicker</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/css-prevent-navigation-layout-shift-flicker</guid>
            <pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Fix common navigation bar layout shifts, scrollbar jumps, font width shifts, and theme switcher flickering with clean CSS and HTML patterns.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Fix common navigation bar layout shifts, scrollbar jumps, font width shifts, and theme switcher flickering with clean CSS and HTML patterns.</p>
</blockquote>
<p>Navigation bars often suffer from subtle layout shifts during page reloads, theme toggles, and tab switches. Combining a few CSS and HTML techniques eliminates these visual jumps entirely.</p>
<h3>1. Lock Root Scrollbar Space and Clip Horizontal Overflow</h3>
<p>Navigating between long pages with scrollbars and short pages without scrollbars causes horizontal layout jumps. Use <code>scrollbar-gutter: stable</code> on <code>html</code> with <code>overflow-y: auto</code>, and clip horizontal overflow on <code>body</code> with <code>overflow-x: clip</code>.</p>
<pre><code class="language-css">/* Reserve scrollbar width globally without forcing a scrollbar track on short pages */
html {
  scrollbar-gutter: stable;
  overflow-y: auto;
}

/* Clip wide horizontal overflow without creating a second scroll container */
body {
  overflow-x: clip;
}</code></pre><p><code>scrollbar-gutter: stable</code> reserves scrollbar space on short pages so layout width remains constant across navigations, while <code>overflow-y: auto</code> avoids rendering an empty scrollbar track when scrolling is not needed.</p>
<p>Use <code>overflow-x: clip</code> instead of <code>overflow-x: hidden</code> on <code>body</code>. Traditional <code>overflow-x: hidden</code> forces browsers to evaluate <code>overflow-y</code> as <code>auto</code>, creating a secondary scroll container on <code>body</code> that causes double vertical scrollbars. <code>overflow-x: clip</code> clips horizontal overflow cleanly without creating a scrolling context.</p>
<h3>2. Prevent View Transition Scrollbar Flicker</h3>
<p>When using native cross-document View Transitions (<code>@view-transition { navigation: auto; }</code>), browsers render outgoing and incoming page snapshots simultaneously. For a single frame during the transition, the viewport height expands, triggering a temporary scrollbar track.</p>
<pre><code class="language-css">/* Prevent temporary scrollbar flicker during cross-document view transitions */
::view-transition-group(root),
::view-transition-image-pair(root),
::view-transition-old(root),
::view-transition-new(root) {
  overflow: hidden !important;
}</code></pre><p>Setting <code>overflow: hidden</code> on the root view transition pseudo-elements clips snapshot layers to viewport bounds, preventing split-second scrollbar popping during page navigations.</p>
<h3>3. Lock Navigation Tab Widths with CSS Grid</h3>
<p>Active or hovered navigation tabs often expand in width when text becomes bold or changes color contrast, pushing adjacent tabs left or right. Use CSS Grid overlay with an invisible pseudo-element to reserve bold text dimensions upfront.</p>
<pre><code class="language-html">&lt;nav class=&quot;nav-tabs&quot;&gt;
  &lt;a href=&quot;/dashboard&quot; class=&quot;nav-link active&quot;&gt;
    &lt;span class=&quot;nav-label&quot; data-text=&quot;Dashboard&quot;&gt;
      &lt;span&gt;Dashboard&lt;/span&gt;
    &lt;/span&gt;
  &lt;/a&gt;
  &lt;a href=&quot;/settings&quot; class=&quot;nav-link&quot;&gt;
    &lt;span class=&quot;nav-label&quot; data-text=&quot;Settings&quot;&gt;
      &lt;span&gt;Settings&lt;/span&gt;
    &lt;/span&gt;
  &lt;/a&gt;
&lt;/nav&gt;</code></pre><pre><code class="language-css">.nav-tabs a {
  display: inline-flex;
  align-items: center;
}

/* Grid overlay sizes container to the widest content layer */
.nav-tabs .nav-label {
  display: inline-grid;
  grid-template-areas: &quot;label&quot;;
  align-items: center;
  justify-items: center;
}

.nav-tabs .nav-label::after,
.nav-tabs .nav-label &gt; span {
  grid-area: label;
}

/* Invisible bold pseudo-element reserves max text width */
.nav-tabs .nav-label::after {
  content: attr(data-text);
  font-weight: 700;
  visibility: hidden;
  overflow: hidden;
  user-select: none;
  pointer-events: none;
}</code></pre><h3>4. Pre-render Theme Toggle Icons in HTML</h3>
<p>Swapping sun and moon icons via JavaScript after page load causes visual button flickering. Pre-render both icons directly in static HTML and toggle visibility using CSS theme classes.</p>
<pre><code class="language-html">&lt;button type=&quot;button&quot; class=&quot;theme-toggle&quot; aria-label=&quot;Toggle theme&quot;&gt;
  &lt;!-- Sun icon visible in dark mode --&gt;
  &lt;svg class=&quot;hidden dark:block&quot; viewBox=&quot;0 0 24 24&quot; width=&quot;16&quot; height=&quot;16&quot;&gt;
    &lt;circle cx=&quot;12&quot; cy=&quot;12&quot; r=&quot;5&quot; fill=&quot;currentColor&quot;/&gt;
  &lt;/svg&gt;

  &lt;!-- Moon icon visible in light mode --&gt;
  &lt;svg class=&quot;block dark:hidden&quot; viewBox=&quot;0 0 24 24&quot; width=&quot;16&quot; height=&quot;16&quot;&gt;
    &lt;path d=&quot;M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z&quot; fill=&quot;currentColor&quot;/&gt;
  &lt;/svg&gt;
&lt;/button&gt;</code></pre><h3>5. Use Opacity for Text Contrast Changes</h3>
<p>Changing text color from gray to solid black or white triggers font stem-darkening in browser rendering engines, altering glyph widths by fractions of a pixel. Use constant base colors and toggle opacity instead.</p>
<pre><code class="language-css">/* Active tab */
.nav-link.active {
  color: #0f172a; /* 100% opacity */
}

/* Inactive tab uses same base color with lower opacity */
.nav-link:not(.active) {
  color: rgba(15, 23, 42, 0.6);
}</code></pre><h3>Key Takeaways</h3>
<ul>
<li>Reserve scrollbar width on <code>html</code> with <code>scrollbar-gutter: stable</code> and clip horizontal overflow on <code>body</code> with <code>overflow-x: clip</code>.</li>
<li>Clip View Transition root pseudo-elements with <code>overflow: hidden</code> to prevent split-second scrollbar popping.</li>
<li>Reserve space for bold tab labels using <code>display: inline-grid</code> and <code>content: attr(data-text)</code>.</li>
<li>Pre-render all theme toggle states in static HTML to eliminate DOM injection flicker.</li>
<li>Adjust text opacity rather than hex colors to preserve font glyph vector calculations.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>CSS</category>
            <category>Styling</category>
            <category>CSS</category>
            <category>Layout Shift</category>
            <category>Frontend</category>
            <category>Performance</category>
        </item>
        <item>
            <title><![CDATA[PHP Predefined and Magic Constants Cheat Sheet (PHP 8.4 Ready)]]></title>
            <link>https://mrpunyapal.dev/blog/php-predefined-constants-cheat-sheet</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/php-predefined-constants-cheat-sheet</guid>
            <pubDate>Sat, 28 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A categorized reference of the most useful constants in PHP with descriptions, error masks, and sample outputs for PHP 8.3 and PHP 8.4.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>A categorized reference of the most useful constants in PHP with descriptions, error masks, and sample outputs for PHP 8.3 and PHP 8.4.</p>
</blockquote>
<p>PHP provides built-in constants for filesystem operations, compile-time metadata, runtime environment information, error levels, and stream handles. These constants are available globally without importing namespaces or requiring setup.</p>
<h2>Filesystem and Path Constants</h2>
<table>
<thead>
<tr>
<th>Constant</th>
<th>Description</th>
<th>Example Output</th>
</tr>
</thead>
<tbody><tr>
<td><code>DIRECTORY_SEPARATOR</code></td>
<td>Path separator (<code>/</code> on Unix, <code>\</code> on Windows)</td>
<td><code>/</code></td>
</tr>
<tr>
<td><code>PATH_SEPARATOR</code></td>
<td>Include path separator (<code>:</code> on Unix, <code>;</code> on Windows)</td>
<td><code>:</code></td>
</tr>
<tr>
<td><code>__FILE__</code></td>
<td>Full filesystem path and filename of the current file</td>
<td><code>/var/www/html/index.php</code></td>
</tr>
<tr>
<td><code>__DIR__</code></td>
<td>Directory path containing the current file</td>
<td><code>/var/www/html</code></td>
</tr>
<tr>
<td><code>__LINE__</code></td>
<td>Current line number in the executing file</td>
<td><code>42</code></td>
</tr>
</tbody></table>
<h2>Magic Compile-Time Constants</h2>
<p>Magic constants are resolved at compile time, introducing zero runtime performance overhead:</p>
<table>
<thead>
<tr>
<th>Constant</th>
<th>Description</th>
<th>Example Output</th>
</tr>
</thead>
<tbody><tr>
<td><code>__FUNCTION__</code></td>
<td>Current function name</td>
<td><code>calculateTotal</code></td>
</tr>
<tr>
<td><code>__CLASS__</code></td>
<td>Current class name with namespace</td>
<td><code>App\Models\Order</code></td>
</tr>
<tr>
<td><code>__TRAIT__</code></td>
<td>Current trait name</td>
<td><code>App\Traits\HasLabel</code></td>
</tr>
<tr>
<td><code>__METHOD__</code></td>
<td>Current class method name</td>
<td><code>App\Services\PaymentService::charge</code></td>
</tr>
<tr>
<td><code>__NAMESPACE__</code></td>
<td>Current namespace</td>
<td><code>App\Services</code></td>
</tr>
</tbody></table>
<h2>PHP Environment and System Limits</h2>
<table>
<thead>
<tr>
<th>Constant</th>
<th>Description</th>
<th>Example Output</th>
</tr>
</thead>
<tbody><tr>
<td><code>PHP_VERSION</code></td>
<td>Full PHP version string</td>
<td><code>8.4.1</code></td>
</tr>
<tr>
<td><code>PHP_MAJOR_VERSION</code></td>
<td>Major version integer</td>
<td><code>8</code></td>
</tr>
<tr>
<td><code>PHP_MINOR_VERSION</code></td>
<td>Minor version integer</td>
<td><code>4</code></td>
</tr>
<tr>
<td><code>PHP_RELEASE_VERSION</code></td>
<td>Patch release integer</td>
<td><code>1</code></td>
</tr>
<tr>
<td><code>PHP_OS</code></td>
<td>Operating system string from PHP build</td>
<td><code>Linux</code>, <code>WINNT</code>, <code>Darwin</code></td>
</tr>
<tr>
<td><code>PHP_OS_FAMILY</code></td>
<td>Portable operating system group</td>
<td><code>Linux</code>, <code>Windows</code>, <code>BSD</code>, <code>Darwin</code></td>
</tr>
<tr>
<td><code>PHP_SAPI</code></td>
<td>Server API interface type</td>
<td><code>cli</code>, <code>fpm-fcgi</code>, <code>apache2handler</code></td>
</tr>
<tr>
<td><code>PHP_EOL</code></td>
<td>Correct newline character for the host OS</td>
<td><code>\n</code> or <code>\r\n</code></td>
</tr>
<tr>
<td><code>PHP_INT_MAX</code></td>
<td>Maximum integer supported by host architecture</td>
<td><code>9223372036854775807</code></td>
</tr>
<tr>
<td><code>PHP_INT_MIN</code></td>
<td>Minimum integer supported by host architecture</td>
<td><code>-9223372036854775808</code></td>
</tr>
<tr>
<td><code>PHP_FLOAT_MAX</code></td>
<td>Maximum floating-point number</td>
<td><code>1.7976931348623E+308</code></td>
</tr>
<tr>
<td><code>PHP_FLOAT_MIN</code></td>
<td>Minimum positive normalized floating-point number</td>
<td><code>2.2250738585072E-308</code></td>
</tr>
</tbody></table>
<h2>Error Reporting Masks</h2>
<table>
<thead>
<tr>
<th>Constant</th>
<th>Description</th>
<th>Category</th>
</tr>
</thead>
<tbody><tr>
<td><code>E_ERROR</code></td>
<td>Fatal runtime errors that halt execution</td>
<td>Fatal</td>
</tr>
<tr>
<td><code>E_WARNING</code></td>
<td>Non-fatal runtime warnings</td>
<td>Warning</td>
</tr>
<tr>
<td><code>E_PARSE</code></td>
<td>Compile-time parse/syntax errors</td>
<td>Fatal</td>
</tr>
<tr>
<td><code>E_NOTICE</code></td>
<td>Runtime notices about potential issues</td>
<td>Notice</td>
</tr>
<tr>
<td><code>E_CORE_ERROR</code></td>
<td>Fatal errors generated by PHP core startup</td>
<td>Fatal</td>
</tr>
<tr>
<td><code>E_CORE_WARNING</code></td>
<td>Warnings generated by PHP core startup</td>
<td>Warning</td>
</tr>
<tr>
<td><code>E_COMPILE_ERROR</code></td>
<td>Fatal errors generated by the Zend script engine</td>
<td>Fatal</td>
</tr>
<tr>
<td><code>E_COMPILE_WARNING</code></td>
<td>Warnings generated by the Zend script engine</td>
<td>Warning</td>
</tr>
<tr>
<td><code>E_USER_ERROR</code></td>
<td>User-generated fatal error via <code>trigger_error()</code></td>
<td>Fatal</td>
</tr>
<tr>
<td><code>E_USER_WARNING</code></td>
<td>User-generated warning via <code>trigger_error()</code></td>
<td>Warning</td>
</tr>
<tr>
<td><code>E_USER_NOTICE</code></td>
<td>User-generated notice via <code>trigger_error()</code></td>
<td>Notice</td>
</tr>
<tr>
<td><code>E_RECOVERABLE_ERROR</code></td>
<td>Catchable fatal error</td>
<td>Fatal (Catchable)</td>
</tr>
<tr>
<td><code>E_DEPRECATED</code></td>
<td>Runtime notices about deprecated features</td>
<td>Warning</td>
</tr>
<tr>
<td><code>E_USER_DEPRECATED</code></td>
<td>User-generated deprecation notice</td>
<td>Warning</td>
</tr>
<tr>
<td><code>E_ALL</code></td>
<td>Bitmask covering all supported error levels</td>
<td>Aggregate</td>
</tr>
</tbody></table>
<h2>File Upload Error Codes</h2>
<p>When processing <code>$_FILES[&#39;upload&#39;][&#39;error&#39;]</code>, compare against these constants instead of magic numbers:</p>
<table>
<thead>
<tr>
<th>Constant</th>
<th>Code</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><code>UPLOAD_ERR_OK</code></td>
<td><code>0</code></td>
<td>File uploaded successfully</td>
</tr>
<tr>
<td><code>UPLOAD_ERR_INI_SIZE</code></td>
<td><code>1</code></td>
<td>File exceeds <code>upload_max_filesize</code> in <code>php.ini</code></td>
</tr>
<tr>
<td><code>UPLOAD_ERR_FORM_SIZE</code></td>
<td><code>2</code></td>
<td>File exceeds <code>MAX_FILE_SIZE</code> specified in HTML form</td>
</tr>
<tr>
<td><code>UPLOAD_ERR_PARTIAL</code></td>
<td><code>3</code></td>
<td>File was only partially uploaded</td>
</tr>
<tr>
<td><code>UPLOAD_ERR_NO_FILE</code></td>
<td><code>4</code></td>
<td>No file was selected for upload</td>
</tr>
<tr>
<td><code>UPLOAD_ERR_NO_TMP_DIR</code></td>
<td><code>6</code></td>
<td>Missing temporary upload folder on server</td>
</tr>
<tr>
<td><code>UPLOAD_ERR_CANT_WRITE</code></td>
<td><code>7</code></td>
<td>Failed to write file to server disk</td>
</tr>
<tr>
<td><code>UPLOAD_ERR_EXTENSION</code></td>
<td><code>8</code></td>
<td>A PHP extension stopped the file upload</td>
</tr>
</tbody></table>
<h2>CLI Stream Constants</h2>
<p>Available automatically in Command Line Interface (CLI) scripts:</p>
<table>
<thead>
<tr>
<th>Constant</th>
<th>Description</th>
<th>Direction</th>
</tr>
</thead>
<tbody><tr>
<td><code>STDIN</code></td>
<td>Open stream resource reading from standard input</td>
<td>Input</td>
</tr>
<tr>
<td><code>STDOUT</code></td>
<td>Open stream resource writing to standard output</td>
<td>Output</td>
</tr>
<tr>
<td><code>STDERR</code></td>
<td>Open stream resource writing to standard error</td>
<td>Output</td>
</tr>
</tbody></table>
<h2>Summary</h2>
<ul>
<li>Use <code>PHP_OS_FAMILY</code> instead of comparing against individual <code>PHP_OS</code> strings for cross-platform compatibility.</li>
<li>Use <code>UPLOAD_ERR_*</code> constants when handling file uploads for descriptive error checking.</li>
<li>Leverage magic constants (<code>__DIR__</code>, <code>__CLASS__</code>, <code>__METHOD__</code>) for zero-overhead compile-time introspection.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>PHP</category>
            <category>Tooling</category>
            <category>PHP</category>
            <category>Constants</category>
            <category>Reference</category>
            <category>PHP 8.4</category>
        </item>
        <item>
            <title><![CDATA[Clean Up Redundant Code Comments with a Custom Rector Rule]]></title>
            <link>https://mrpunyapal.dev/blog/php-rector-remove-unnecessary-comments</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/php-rector-remove-unnecessary-comments</guid>
            <pubDate>Wed, 11 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Use a custom Rector rule to automatically strip noisy inline comments and empty docblocks across your codebase while preserving essential PHPDoc annotations.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Use a custom Rector rule to automatically strip noisy inline comments and empty docblocks across your codebase while preserving essential PHPDoc annotations.</p>
</blockquote>
<p>Codebases often accumulate redundant comments over time: commented-out legacy code, obvious explanations (<code>// set user name</code>), or empty boilerplate docblocks generated by older IDE templates.</p>
<p>Manually reviewing and removing thousands of stale comments across hundreds of files is time-consuming. Using Rector, you can define an AbstractRector rule that strips non-essential comments while retaining critical PHPDoc annotations such as <code>@param</code>, <code>@return</code>, and <code>@throws</code>.</p>
<h2>The Rector Implementation</h2>
<pre><code class="language-php">namespace App\Rector;

use PhpParser\Comment;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

final class RemoveUnnecessaryCommentsRector extends AbstractRector
{
    // Whitelist specific comment strings or tags you want to keep
    private const ALLOWED_PATTERNS = [
        &#039;@todo&#039;,
        &#039;@var&#039;,
    ];

    public function getNodeTypes(): array
    {
        return [Node::class];
    }

    public function refactor(Node $node): ?Node
    {
        $comments = $node-&gt;getComments();

        if ($comments === []) {
            return null;
        }

        $filtered = [];
        $changed = false;

        foreach ($comments as $comment) {
            if ($comment instanceof Doc) {
                $cleaned = $this-&gt;cleanDocBlock($comment);

                if ($cleaned !== $comment-&gt;getText()) {
                    if ($cleaned !== &#039;&#039;) {
                        $filtered[] = new Doc($cleaned);
                    }
                    $changed = true;
                } else {
                    $filtered[] = $comment;
                }
                continue;
            }

            // Check if regular comment contains allowed keywords
            if ($this-&gt;isAllowed($comment-&gt;getText())) {
                $filtered[] = $comment;
            } else {
                $changed = true;
            }
        }

        if (! $changed) {
            return null;
        }

        $node-&gt;setAttribute(&#039;comments&#039;, $filtered);

        return $node;
    }

    private function cleanDocBlock(Doc $doc): string
    {
        $lines = preg_split(&#039;/\R/&#039;, $doc-&gt;getText());
        $tags = [];

        foreach ($lines as $line) {
            $line = trim(preg_replace(&#039;/^\s*\*\s?/&#039;, &#039;&#039;, $line));

            if ($line === &#039;&#039;) {
                continue;
            }

            // Preserve standard PHPDoc annotations (@param, @return, @throws, etc.)
            if (str_starts_with($line, &#039;@&#039;)) {
                $tags[] = $line;
            }
        }

        if ($tags === []) {
            return &#039;&#039;;
        }

        return &quot;/**\n * &quot; . implode(&quot;\n * &quot;, $tags) . &quot;\n */&quot;;
    }

    private function isAllowed(string $text): bool
    {
        foreach (self::ALLOWED_PATTERNS as $allowed) {
            if (str_contains($text, $allowed)) {
                return true;
            }
        }

        return false;
    }

    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition(&#039;Remove redundant inline comments while retaining essential PHPDoc tags&#039;, [
            new CodeSample(
                &lt;&lt;&lt;&#039;CODE&#039;
                // Initialize user model
                $user = new User();
                CODE
                ,
                &lt;&lt;&lt;&#039;CODE&#039;
                $user = new User();
                CODE
            ),
        ]);
    }
}</code></pre><h2>Registering in <code>rector.php</code></h2>
<p>Add the custom rule to your project&#39;s Rector configuration:</p>
<pre><code class="language-php">use App\Rector\RemoveUnnecessaryCommentsRector;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    -&gt;withRules([
        RemoveUnnecessaryCommentsRector::class,
    ]);</code></pre><p>Run the refactoring dry-run to preview changes:</p>
<pre><code class="language-bash">vendor/bin/rector process --dry-run</code></pre><h2>Summary</h2>
<ul>
<li>Use AST-based comment manipulation in Rector to systematically clean comment noise across large codebases.</li>
<li>The rule strips redundant plain comments (<code>// comment</code>) while keeping meaningful docblock tags (<code>@param</code>, <code>@return</code>, <code>@throws</code>).</li>
<li>Easily extensible to whitelist specific tags or maintenance markers such as <code>@todo</code>.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>PHP</category>
            <category>Tooling</category>
            <category>PHP</category>
            <category>Rector</category>
            <category>Refactoring</category>
            <category>Clean Code</category>
            <category>Tooling</category>
        </item>
        <item>
            <title><![CDATA[Reusable Tappable Scopes for Eloquent and Laravel Scout Queries]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-tappable-scopes-eloquent-scout</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-tappable-scopes-eloquent-scout</guid>
            <pubDate>Fri, 07 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Extract query filtering logic into invokable scope classes that work interchangeably across Eloquent query builders and Laravel Scout search instances.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Extract query filtering logic into invokable scope classes that work interchangeably across Eloquent query builders and Laravel Scout search instances.</p>
</blockquote>
<p>Standard Eloquent local scopes (<code>scopeActive()</code>, <code>scopeFilter()</code>) work well for database queries, but they are tightly coupled to the Eloquent <code>Builder</code> instance and cannot be directly invoked on a Laravel Scout search query builder (<code>Post::search()-&gt;...</code>).</p>
<p>By creating invokable &quot;Tappable Scope&quot; classes, you can share identical query constraints across both database queries and full-text search pipelines without duplicating logic.</p>
<h2>1. Create the Invokable Scope Class</h2>
<p>Create a dedicated scope class that accepts either an Eloquent <code>Builder</code> or a Scout <code>Builder</code>:</p>
<pre><code class="language-php">namespace App\Models\Scopes;

use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use LaravelScoutBuilder as ScoutBuilder;

final class PublishedScope
{
    public function __construct(
        private ?User $user = null
    ) {}

    public function __invoke(Builder|ScoutBuilder $query): Builder|ScoutBuilder
    {
        return $query
            -&gt;where(&#039;is_published&#039;, true)
            -&gt;when($this-&gt;user, fn ($q) =&gt; $q-&gt;where(&#039;author_id&#039;, $this-&gt;user-&gt;id));
    }
}</code></pre><h2>2. Using in Model Local Scopes</h2>
<p>Apply the scope class inside your model&#39;s local scope using <code>$query-&gt;tap()</code>:</p>
<pre><code class="language-php">namespace App\Models;

use App\Models\Scopes\PublishedScope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    public function scopePublished(Builder $query, ?User $user = null): Builder
    {
        return $query-&gt;tap(new PublishedScope($user));
    }
}</code></pre><h2>3. Using in Laravel Scout Search Queries</h2>
<p>The same scope class can be piped directly into Scout search queries via <code>-&gt;tap()</code>:</p>
<pre><code class="language-php">use App\Models\Post;
use App\Models\Scopes\PublishedScope;

// Search posts with the same publishing constraints applied
$results = Post::search($searchTerm)
    -&gt;tap(new PublishedScope(auth()-&gt;user()))
    -&gt;get();</code></pre><h2>Why Use Tappable Scopes?</h2>
<ul>
<li><strong>Dual Compatibility</strong>: Operates directly across both standard Eloquent queries and Laravel Scout search pipelines.</li>
<li><strong>Cross-Model Reuse</strong>: Share identical filtering rules across multiple models (e.g. <code>Post</code>, <code>Article</code>, <code>Video</code>) without bloated traits.</li>
<li><strong>Cleaner Models</strong>: Keeps model files lightweight by extracting complex filtering criteria into dedicated, testable classes.</li>
</ul>
<h2>Summary</h2>
<ul>
<li>Use invokable scope classes paired with <code>-&gt;tap()</code> to share query logic between Eloquent and Scout.</li>
<li>Keeps search filtering synchronized with database query scopes.</li>
<li>Eliminates duplicate query logic and keeps model classes lean.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Eloquent</category>
            <category>Laravel</category>
            <category>Eloquent</category>
            <category>Scout</category>
            <category>Architecture</category>
            <category>DRY</category>
        </item>
        <item>
            <title><![CDATA[Typed Arrays and Array Shapes in PHP with PHPDoc]]></title>
            <link>https://mrpunyapal.dev/blog/php-array-shapes-phpdoc</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/php-array-shapes-phpdoc</guid>
            <pubDate>Thu, 25 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Document and enforce structured array schemas using PHPDoc array shapes, lists, and non-empty array annotations for static analysis in PHPStan and Psalm.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Document and enforce structured array schemas using PHPDoc array shapes, lists, and non-empty array annotations for static analysis in PHPStan and Psalm.</p>
</blockquote>
<p>PHP arrays are versatile: they function as zero-indexed lists, associative maps, and nested dictionary structures. However, using native typehints like <code>function process(array $data)</code> leaves the contents of the array untyped, leading to potential missing key errors or unexpected values at runtime.</p>
<p>By using PHPDoc array shapes and pseudo-types, static analysis tools (like PHPStan and Psalm) and modern IDEs (such as PhpStorm and VS Code) can strictly validate array keys and types without requiring dedicated Data Transfer Objects (DTOs) for every small payload.</p>
<hr>
<h2>1. Defining Exact Array Shapes (<code>array{...}</code>)</h2>
<p>Array shapes define the exact dictionary schema of an associative array, including expected keys and their individual types:</p>
<pre><code class="language-php">/**
 * @param array{id: int, name: string, email: string, is_active?: bool} $user
 * @return array{status: string, timestamp: int}
 */
function registerUser(array $user): array
{
    // PHPStan ensures $user[&#039;id&#039;], $user[&#039;name&#039;], and $user[&#039;email&#039;] exist
    $status = $user[&#039;is_active&#039;] ?? true ? &#039;active&#039; : &#039;pending&#039;;

    return [
        &#039;status&#039; =&gt; $status,
        &#039;timestamp&#039; =&gt; time(),
    ];
}</code></pre><ul>
<li><strong>Required Keys</strong>: <code>id: int, name: string</code></li>
<li><strong>Optional Keys (<code>?</code>)</strong>: <code>is_active?: bool</code> (the key may or may not be present in the array)</li>
</ul>
<hr>
<h2>2. Zero-Indexed Lists (<code>list&lt;T&gt;</code>)</h2>
<p>Standard PHP arrays can have arbitrary or sparse integer keys. When an array is guaranteed to be a contiguous, zero-indexed numerical list (<code>0, 1, 2, ...</code>), use <code>list&lt;T&gt;</code>:</p>
<pre><code class="language-php">/**
 * @param list&lt;int&gt; $scores
 * @return float
 */
function calculateAverage(array $scores): float
{
    if ($scores === []) {
        return 0.0;
    }

    return array_sum($scores) / count($scores);
}

// Valid list
calculateAverage([90, 85, 95]);

// PHPStan flags non-list associative arrays
calculateAverage([&#039;first&#039; =&gt; 90, &#039;second&#039; =&gt; 85]); // Error</code></pre><hr>
<h2>3. Associative Key-Value Maps (<code>array&lt;K, V&gt;</code>)</h2>
<p>When working with dynamic key-value maps (such as configuration options or lookup tables):</p>
<pre><code class="language-php">/**
 * @param array&lt;string, mixed&gt; $payload
 * @return string
 */
function serializePayload(array $payload): string
{
    return json_encode($payload, JSON_THROW_ON_ERROR);
}

/**
 * @param array&lt;int, User&gt; $usersById
 */
function notifyUsers(array $usersById): void
{
    foreach ($usersById as $id =&gt; $user) {
        // $id is typed as int, $user is typed as User
        $user-&gt;sendNotification();
    }
}</code></pre><hr>
<h2>4. Non-Empty Arrays (<code>non-empty-array&lt;T&gt;</code>)</h2>
<p>If a function requires at least one element to prevent division-by-zero or empty-set errors, declare it with <code>non-empty-array</code>:</p>
<pre><code class="language-php">/**
 * @param non-empty-array&lt;string&gt; $recipients
 * @return string
 */
function getPrimaryRecipient(array $recipients): string
{
    // Guaranteed to contain at least 1 element; safe from undefined offset
    return $recipients[0];
}</code></pre><hr>
<h2>5. Typed Callback Arrays (<code>array&lt;callable&gt;</code>)</h2>
<p>When storing or dispatching collections of closures or invokable handlers:</p>
<pre><code class="language-php">/**
 * @param list&lt;callable(): void&gt; $listeners
 */
function triggerListeners(array $listeners): void
{
    foreach ($listeners as $listener) {
        $listener();
    }
}

triggerListeners([
    fn () =&gt; logger()-&gt;info(&#039;First listener executed&#039;),
    fn () =&gt; logger()-&gt;info(&#039;Second listener executed&#039;),
]);</code></pre><hr>
<h2>Summary</h2>
<ul>
<li>Use <code>array{key: type}</code> to define explicit key requirements and prevent missing array index bugs.</li>
<li>Use <code>list&lt;T&gt;</code> for contiguous, zero-indexed sequential lists.</li>
<li>Use <code>array&lt;KeyType, ValueType&gt;</code> for generic maps and associative containers.</li>
<li>Use <code>non-empty-array&lt;T&gt;</code> to statically assert that an array contains at least one item.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>PHP</category>
            <category>Tooling</category>
            <category>PHP</category>
            <category>PHPStan</category>
            <category>Type Safety</category>
            <category>Arrays</category>
            <category>Tooling</category>
        </item>
        <item>
            <title><![CDATA[Generic Classes and Functions in PHP Using PHPDoc @template]]></title>
            <link>https://mrpunyapal.dev/blog/php-generics-phpdoc-template</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/php-generics-phpdoc-template</guid>
            <pubDate>Thu, 25 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Implement type-safe generic collections, wrapper classes, and utility functions in PHP using PHPDoc template annotations for static analysis engines.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Implement type-safe generic collections, wrapper classes, and utility functions in PHP using PHPDoc template annotations for static analysis engines.</p>
</blockquote>
<p>While PHP does not have native language-level generic syntax (like <code>class Collection&lt;T&gt;</code>), static analysis tools such as PHPStan and Psalm provide full generic support via <code>@template</code> annotations.</p>
<p>By annotating wrapper classes and helper functions with <code>@template</code>, you preserve exact object types across transformations and collections without resorting to ambiguous <code>mixed</code> or <code>object</code> returns.</p>
<hr>
<h2>1. Generic Collections and Repositories</h2>
<p>Annotate a class with <code>@template T</code> so the items stored inside match the items returned:</p>
<pre><code class="language-php">/**
 * @template T
 */
class Collection
{
    /** @var list&lt;T&gt; */
    private array $items = [];

    /**
     * @param T $item
     */
    public function add(mixed $item): void
    {
        $this-&gt;items[] = $item;
    }

    /**
     * @return list&lt;T&gt;
     */
    public function all(): array
    {
        return $this-&gt;items;
    }

    /**
     * @return T|null
     */
    public function first(): mixed
    {
        return $this-&gt;items[0] ?? null;
    }
}</code></pre><h3>Usage with Type Preservation</h3>
<pre><code class="language-php">/** @var Collection&lt;User&gt; $userCollection */
$userCollection = new Collection();
$userCollection-&gt;add(new User(&#039;Alice&#039;));

// IDE and PHPStan know $user is an instance of User
$user = $userCollection-&gt;first();
$userName = $user?-&gt;name;</code></pre><hr>
<h2>2. Generic Type Constraints (<code>@template T of BaseClass</code>)</h2>
<p>You can restrict a generic type parameter to a specific class, interface, or union type using the <code>of</code> keyword:</p>
<pre><code class="language-php">abstract class Animal
{
    abstract public function speak(): string;
}

class Dog extends Animal
{
    public function speak(): string { return &#039;Woof&#039;; }
}

class Cat extends Animal
{
    public function speak(): string { return &#039;Meow&#039;; }
}

/**
 * @template T of Animal
 */
class Shelter
{
    /** @var list&lt;T&gt; */
    private array $residents = [];

    /**
     * @param T $animal
     */
    public function admit(Animal $animal): void
    {
        $this-&gt;residents[] = $animal;
    }

    /**
     * @return list&lt;T&gt;
     */
    public function getResidents(): array
    {
        return $this-&gt;residents;
    }
}</code></pre><hr>
<h2>3. Generic Functions and Utility Methods</h2>
<p>Generic functions capture the type passed in arguments and return that exact same type:</p>
<pre><code class="language-php">/**
 * @template T
 * @param non-empty-array&lt;T&gt; $items
 * @return T
 */
function getFirstItem(array $items): mixed
{
    return $items[array_key_first($items)];
}

// Inferred return type: int
$number = getFirstItem([10, 20, 30]);

// Inferred return type: Product
$product = getFirstItem([new Product(&#039;Keyboard&#039;), new Product(&#039;Mouse&#039;)]);</code></pre><hr>
<h2>4. Generic Factory Methods with <code>class-string&lt;T&gt;</code></h2>
<p>When instantiating or resolving classes dynamically by class name, combine <code>@template T</code> with <code>class-string&lt;T&gt;</code>:</p>
<pre><code class="language-php">/**
 * @template T of object
 * @param class-string&lt;T&gt; $className
 * @return T
 */
function createInstance(string $className): object
{
    return new $className();
}

// $service is statically typed as PaymentGateway, with full autocomplete
$service = createInstance(PaymentGateway::class);</code></pre><hr>
<h2>Summary</h2>
<ul>
<li>Use <code>@template T</code> on classes and functions to capture dynamic types and prevent type degradation to <code>mixed</code>.</li>
<li>Use <code>@template T of Constraint</code> to enforce class hierarchy or interface boundaries on generic parameters.</li>
<li>Combine <code>@template T</code> with <code>class-string&lt;T&gt;</code> for strongly typed factory and dependency injection methods.</li>
<li>Enables compile-time type safety across complex collections and architectures.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>PHP</category>
            <category>Tooling</category>
            <category>PHP</category>
            <category>Generics</category>
            <category>PHPStan</category>
            <category>Type Safety</category>
            <category>Architecture</category>
        </item>
        <item>
            <title><![CDATA[Complete Modern PHP Type System Cheat Sheet]]></title>
            <link>https://mrpunyapal.dev/blog/php-type-system-cheat-sheet</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/php-type-system-cheat-sheet</guid>
            <pubDate>Thu, 25 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[A reference guide to scalar, compound, special, user-defined, union, and intersection types in modern PHP.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>A reference guide to scalar, compound, special, user-defined, union, and intersection types in modern PHP.</p>
</blockquote>
<p>PHP features a gradual and expressive type system. From scalar type declarations introduced in PHP 7 to union types in PHP 8.0, intersection types in PHP 8.1, and standalone <code>true</code>, <code>false</code>, and <code>null</code> types in PHP 8.2, PHP provides reliable type safety at runtime and compile time.</p>
<hr>
<h2>1. Atomic and Scalar Types</h2>
<p>Scalar types represent basic primitive values:</p>
<pre><code class="language-php">// Scalar Types
$active   = true;              // bool (or literal true / false)
$quantity = 42;                // int
$price    = 19.99;             // float
$name     = &#039;Punyapal&#039;;        // string

// Built-in Special Types
$empty    = null;              // null
$items    = [1, 2, 3];         // array
$payload  = new stdClass();    // object
$handle   = fopen(&#039;php://memory&#039;, &#039;r+&#039;); // resource</code></pre><hr>
<h2>2. Special Return Types</h2>
<p>PHP supports specialized return types that describe function termination and inheritance contexts:</p>
<pre><code class="language-php">// void: Function executes without returning a value
function logMessage(string $msg): void
{
    echo $msg;
}

// never: Function always exits or throws an exception, never finishing normally
function abortWithError(string $error): never
{
    throw new RuntimeException($error);
}

// Relative Class Types
class Model
{
    // self: Returns an instance of the exact class where the method is defined
    public function createSelf(): self
    {
        return new self();
    }

    // static: Returns an instance of the calling class (respects Late Static Binding)
    public static function make(): static
    {
        return new static();
    }
}

class User extends Model
{
    // parent: References the immediate parent class
    public function getBaseModel(): parent
    {
        return new parent();
    }
}</code></pre><hr>
<h2>3. User-Defined Types</h2>
<p>Types created within application domain code:</p>
<pre><code class="language-php">// Interfaces
interface Notifiable
{
    public function notify(): void;
}

// Concrete Classes
class Invoice implements Notifiable
{
    public function notify(): void {}
}

// Backed &amp; Pure Enums (PHP 8.1+)
enum OrderStatus: string
{
    case Draft = &#039;draft&#039;;
    case Paid  = &#039;paid&#039;;
}</code></pre><hr>
<h2>4. Callables and Invocations</h2>
<pre><code class="language-php">// Closure or invokable class instance
function executeTask(callable $task): void
{
    $task();
}

executeTask(fn () =&gt; print(&quot;Task completed\n&quot;));</code></pre><hr>
<h2>5. Composite Types: Unions and Intersections</h2>
<h3>Union Types (<code>A|B</code>)</h3>
<p>Allows an argument or return value to satisfy any of the listed types:</p>
<pre><code class="language-php">function findUser(int|string $identifier): ?User
{
    return is_int($identifier)
        ? User::find($identifier)
        : User::where(&#039;email&#039;, $identifier)-&gt;first();
}</code></pre><h3>Intersection Types (<code>A&amp;B</code>)</h3>
<p>Requires an object to satisfy all listed interfaces simultaneously:</p>
<pre><code class="language-php">interface HasId { public function getId(): int; }
interface HasName { public function getName(): string; }

function displayEntity(HasId&amp;HasName $entity): string
{
    return &quot;#{$entity-&gt;getId()}: {$entity-&gt;getName()}&quot;;
}</code></pre><h3>Disjunctive Normal Form (DNF) Types (PHP 8.2+)</h3>
<p>Combines Union and Intersection types using parentheses:</p>
<pre><code class="language-php">// Accepts (HasId AND HasName) OR null
function formatRecord((HasId&amp;HasName)|null $record): string
{
    return $record ? displayEntity($record) : &#039;Anonymous&#039;;
}</code></pre><hr>
<h2>6. Top Types: <code>mixed</code> and <code>iterable</code></h2>
<pre><code class="language-php">// mixed: Equivalent to array|bool|callable|int|float|null|object|resource|string
function debugValue(mixed $value): void
{
    var_dump($value);
}

// iterable: Accepts both arrays and objects implementing Traversable (like Generators or Collections)
function processBatch(iterable $items): void
{
    foreach ($items as $item) {
        // ...
    }
}</code></pre><hr>
<h2>Summary</h2>
<ul>
<li>Use scalar and atomic types (<code>int</code>, <code>string</code>, <code>bool</code>, <code>float</code>) for basic primitives.</li>
<li>Use <code>void</code> when no value is returned, and <code>never</code> for methods that always terminate or throw.</li>
<li>Use <code>static</code> return types for fluent chaining and late static binding in parent classes.</li>
<li>Use Union types (<code>int|string</code>) and Intersection types (<code>InterfaceA&amp;InterfaceB</code>) for precise polymorphic contracts.</li>
<li>Use <code>iterable</code> to accept both arrays and <code>Traversable</code> collections directly.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>PHP</category>
            <category>Syntax</category>
            <category>PHP</category>
            <category>Types</category>
            <category>Syntax</category>
            <category>Reference</category>
            <category>Type Safety</category>
        </item>
        <item>
            <title><![CDATA[Complete Guide to PHPStan Static Analysis Levels (0 to 10)]]></title>
            <link>https://mrpunyapal.dev/blog/phpstan-static-analysis-levels-guide</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/phpstan-static-analysis-levels-guide</guid>
            <pubDate>Thu, 25 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Understand what each rule level in PHPStan inspects, from basic syntax errors at Level 0 to strict mixed-type enforcement at Level 9 and Level 10.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Understand what each rule level in PHPStan inspects, from basic syntax errors at Level 0 to strict mixed-type enforcement at Level 9 and Level 10.</p>
</blockquote>
<p>PHPStan inspects your PHP code without executing it, discovering subtle type mismatches, dead code, and unhandled edge cases. PHPStan organizes its rule sets into numeric levels ranging from <strong>0 (most lenient)</strong> to <strong>10 (maximum strictness)</strong>.</p>
<p>When adopting PHPStan in existing or greenfield projects, understanding what each level verifies helps you establish progressive static analysis milestones.</p>
<h2>Overview of PHPStan Levels</h2>
<table>
<thead>
<tr>
<th>Level</th>
<th>Focus Area</th>
<th>What It Detects</th>
</tr>
</thead>
<tbody><tr>
<td><strong>0</strong></td>
<td>Basic Checks</td>
<td>Unknown classes, non-existent methods, wrong argument counts</td>
</tr>
<tr>
<td><strong>1</strong></td>
<td>Undefined Variables</td>
<td>Variables that might be undefined in certain code branches</td>
</tr>
<tr>
<td><strong>2</strong></td>
<td>Unknown Methods &amp; Properties</td>
<td>Validates methods/properties on all expressions and PHPDoc types</td>
</tr>
<tr>
<td><strong>3</strong></td>
<td>Return &amp; Property Types</td>
<td>Ensures return types and property assignments match declared types</td>
</tr>
<tr>
<td><strong>4</strong></td>
<td>Dead Code Checks</td>
<td>Unreachable <code>else</code> branches, redundant <code>instanceof</code> checks</td>
</tr>
<tr>
<td><strong>5</strong></td>
<td>Function &amp; Method Arguments</td>
<td>Verifies argument types passed to methods against signatures</td>
</tr>
<tr>
<td><strong>6</strong></td>
<td>Missing Typehints</td>
<td>Reports missing parameter and return type declarations</td>
</tr>
<tr>
<td><strong>7</strong></td>
<td>Union Type Accuracy</td>
<td>Detects partially wrong union types (calling a method only on one variant)</td>
</tr>
<tr>
<td><strong>8</strong></td>
<td>Nullable Type Safety</td>
<td>Flags method calls and property accesses on potentially null values</td>
</tr>
<tr>
<td><strong>9</strong></td>
<td>Strict <code>mixed</code> Evaluation</td>
<td>Prohibits calling methods or accessing properties on <code>mixed</code> without narrowing</td>
</tr>
<tr>
<td><strong>10</strong></td>
<td>Strict Explicit <code>mixed</code> Types</td>
<td>Prohibits passing or returning <code>mixed</code> where specific types are expected</td>
</tr>
</tbody></table>
<hr>
<h2>Level-by-Level Code Examples</h2>
<h3>Level 0: Basic Syntax &amp; Function Calls</h3>
<p>Verifies that called classes, functions, and methods exist, and that required parameter counts are satisfied:</p>
<pre><code class="language-php">class Example
{
    public function run(): void
    {
        $this-&gt;unknownMethod(); // Error: Call to an undefined method
    }
}

function calculate(int $a, int $b): int
{
    return $a + $b;
}

calculate(1); // Error: Function calculate() called with 1 parameter, 2 required.</code></pre><h3>Level 1: Possibly Undefined Variables</h3>
<p>Ensures every variable accessed is guaranteed to be defined in all branching paths:</p>
<pre><code class="language-php">function process(bool $flag): void
{
    if ($flag) {
        $data = &#039;ready&#039;;
    }

    echo $data; // Error: Variable $data might not be defined.
}</code></pre><h3>Level 2: Unknown Methods on All Expressions</h3>
<p>Checks methods and properties on expressions where types are known via annotations or reflection:</p>
<pre><code class="language-php">class User
{
    public function getName(): string { return &#039;Alex&#039;; }
}

$user = new User();
$user-&gt;getProfilePhoto(); // Error: Call to an undefined method User::getProfilePhoto()</code></pre><h3>Level 3: Return Types and Property Assignments</h3>
<p>Validates that returned values and assigned properties match their type definitions:</p>
<pre><code class="language-php">class Order
{
    private int $total;

    public function setTotal(string $amount): void
    {
        $this-&gt;total = $amount; // Error: Property Order::$total (int) does not accept string.
    }

    public function getInvoiceNumber(): string
    {
        return 12345; // Error: Method getInvoiceNumber() should return string but returns int.
    }
}</code></pre><h3>Level 4: Dead Code and Redundant Logic</h3>
<p>Detects unreachable statements and conditionals that always evaluate to true or false:</p>
<pre><code class="language-php">function check(User $user): void
{
    if ($user instanceof User) { // Always true
        echo &#039;Valid&#039;;
    }

    return;
    echo &#039;Unreachable&#039;; // Error: Unreachable statement - code above always returns.
}</code></pre><h3>Level 5: Argument Type Validation</h3>
<p>Verifies that arguments passed to functions match the declared parameter types:</p>
<pre><code class="language-php">function setDiscount(int $percentage): void {}

setDiscount(&#039;20&#039;); // Error: Parameter #1 expects int, string given.</code></pre><h3>Level 6: Missing Typehints</h3>
<p>Requires explicit typehints on function parameters, return types, and properties:</p>
<pre><code class="language-php">class Report
{
    // Error: Property Report::$records has no type specified.
    private $records;

    // Error: Method generate() has parameter $options with no type specified.
    public function generate($options) {}
}</code></pre><h3>Level 7: Partially Wrong Union Types</h3>
<p>Flags method calls on union types where only some union members implement the method:</p>
<pre><code class="language-php">class Customer { public function sendInvoice(): void {} }
class Guest {}

function notify(Customer|Guest $recipient): void
{
    // Error: Call to method sendInvoice() on Customer|Guest (Guest does not have method).
    $recipient-&gt;sendInvoice();
}</code></pre><h3>Level 8: Nullable Type Access</h3>
<p>Catches potential &quot;Call to a member function on null&quot; errors:</p>
<pre><code class="language-php">function printLength(?string $input): int
{
    // Error: Cannot call method strlen() with parameter ?string (requires string).
    return strlen($input);
}</code></pre><h3>Level 9: Strict <code>mixed</code> Evaluation</h3>
<p>At Level 9, <code>mixed</code> is treated strictly. You cannot perform operations on <code>mixed</code> values without explicit type narrowing:</p>
<pre><code class="language-php">function formatData(mixed $value): string
{
    // Error: Cannot call method trim() on mixed. Narrow type with is_string() first.
    return trim($value);
}</code></pre><h3>Level 10: Strict Explicit <code>mixed</code> Types (PHPStan 2.0+)</h3>
<p>PHPStan 2.0 introduces Level 10 (or <code>level: max</code>), which goes beyond Level 9 by prohibiting passing <code>mixed</code> values or returning <code>mixed</code> where specific types are expected, ensuring full end-to-end type safety throughout generic containers and arrays:</p>
<pre><code class="language-php">/**
 * @param array&lt;string, mixed&gt; $payload
 */
function processPayload(array $payload): string
{
    // Level 10 Error: Parameter #1 $name of function greet() expects string, mixed given.
    return greet($payload[&#039;name&#039;]);
}

function greet(string $name): string
{
    return &quot;Hello, {$name}!&quot;;
}</code></pre><p>To satisfy Level 10, explicitly assert or validate the type:</p>
<pre><code class="language-php">function processPayload(array $payload): string
{
    if (! isset($payload[&#039;name&#039;]) || ! is_string($payload[&#039;name&#039;])) {
        throw new InvalidArgumentException(&#039;Expected name to be a string.&#039;);
    }

    return greet($payload[&#039;name&#039;]); // Valid at Level 10
}</code></pre><h2>Recommended Adoption Strategy</h2>
<ol>
<li><strong>Start at Level 1 or 2</strong>: Fix baseline undefined variables and non-existent methods in legacy codebases.</li>
<li><strong>Advance to Level 5</strong>: Add argument type verification to prevent runtime TypeError exceptions.</li>
<li><strong>Target Level 8 or Level 10</strong>: Ideal for modern, strongly typed Laravel and PHP applications using Larastan.</li>
</ol>
<h2>Summary</h2>
<ul>
<li>PHPStan levels range progressively from 0 (lenient) to 10 (maximum strictness).</li>
<li>Levels 1 to 5 catch common runtime bugs without requiring exhaustive PHPDoc annotations.</li>
<li>Levels 6 to 8 enforce complete type coverage and nullable safety.</li>
<li>Levels 9 and 10 enforce strict type narrowing on <code>mixed</code> values and array payloads.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>PHP</category>
            <category>Tooling</category>
            <category>PHP</category>
            <category>PHPStan</category>
            <category>Tooling</category>
            <category>Static Analysis</category>
            <category>Type Safety</category>
        </item>
        <item>
            <title><![CDATA[Keep Controllers Lean by Delegating Business Logic to Action Classes]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-architecture-skinny-controllers-invokable-actions</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-architecture-skinny-controllers-invokable-actions</guid>
            <pubDate>Wed, 10 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Refactor fat controller methods by moving multi-step transactions, file storage, and third-party integrations into invokable Action classes.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Refactor fat controller methods by moving multi-step transactions, file storage, and third-party integrations into invokable Action classes.</p>
</blockquote>
<p>When a controller handles file uploads, payment transactions, database record creation, and email notifications in a single method, testing individual business steps becomes difficult and duplicates logic across CLI commands or API endpoints.</p>
<p>Moving domain logic into dedicated, invokable Action classes creates modular, reusable application layers.</p>
<h2>Fat Controller (Anti-Pattern)</h2>
<pre><code class="language-php">public function store(StoreOrderRequest $request)
{
    // ❌ Fat controller mixing validation, billing, file storage, and events
    $payment = Stripe::charges()-&gt;create([...]);
    $order = Order::create([...]);
    $invoicePdf = PDF::generate($order);
    Storage::disk(&#039;s3&#039;)-&gt;put(&quot;invoices/{$order-&gt;id}.pdf&quot;, $invoicePdf);
    Mail::to($request-&gt;user())-&gt;send(new OrderConfirmation($order));

    return to_route(&#039;orders.show&#039;, $order);
}</code></pre><h2>Refactored Action Class</h2>
<pre><code class="language-php">namespace App\Actions\Orders;

use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class CreateOrderAction
{
    public function __construct(
        protected PaymentGateway $gateway,
        protected InvoiceGenerator $invoices
    ) {}

    public function execute(User $user, array $orderData, string $paymentToken): Order
    {
        return DB::transaction(function () use ($user, $orderData, $paymentToken) {
            $charge = $this-&gt;gateway-&gt;charge($user, $orderData[&#039;total&#039;], $paymentToken);
            $order = $user-&gt;orders()-&gt;create([...$orderData, &#039;transaction_id&#039; =&gt; $charge-&gt;id]);
            $this-&gt;invoices-&gt;generateAndStore($order);

            return $order;
        });
    }
}</code></pre><h2>Skinny Controller</h2>
<pre><code class="language-php">public function store(StoreOrderRequest $request, CreateOrderAction $action)
{
    $order = $action-&gt;execute(
        $request-&gt;user(),
        $request-&gt;validated(),
        $request-&gt;input(&#039;payment_token&#039;)
    );

    return to_route(&#039;orders.show&#039;, $order)-&gt;with(&#039;success&#039;, &#039;Order created.&#039;);
}</code></pre><h2>Summary</h2>
<ul>
<li>Action classes isolate core business rules from HTTP transport details.</li>
<li>Reusable across web controllers, API endpoints, queue workers, and Artisan commands.</li>
<li>Easily testable with isolated unit test mocks.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Architecture</category>
            <category>Laravel</category>
            <category>Architecture</category>
            <category>Controllers</category>
            <category>Design Patterns</category>
        </item>
        <item>
            <title><![CDATA[Follow Laravel Standard Naming Conventions for Predictable Architecture]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-architecture-naming-conventions-reference</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-architecture-naming-conventions-reference</guid>
            <pubDate>Wed, 05 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[A complete reference guide to Laravel community naming conventions across models, controllers, migrations, relationships, routes, views, enums, and tests.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>A complete reference guide to Laravel community naming conventions across models, controllers, migrations, relationships, routes, views, enums, and tests.</p>
</blockquote>
<p>Following standard naming conventions allows Laravel&#39;s internal reflection engines (including route model binding, Eloquent relation resolution, and migration schema builders) to function automatically without manual configuration.</p>
<h2>Complete Naming Conventions Cheat Sheet</h2>
<table>
<thead>
<tr>
<th>Component</th>
<th>Convention</th>
<th>Good Example</th>
<th>Bad Example</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Controller</strong></td>
<td>Singular (PascalCase)</td>
<td><code>ArticleController</code></td>
<td><code>ArticlesController</code></td>
</tr>
<tr>
<td><strong>Model</strong></td>
<td>Singular (PascalCase)</td>
<td><code>User</code>, <code>Order</code></td>
<td><code>Users</code>, <code>Orders</code></td>
</tr>
<tr>
<td><strong>Migration Table</strong></td>
<td>Plural (snake_case)</td>
<td><code>articles</code>, <code>order_items</code></td>
<td><code>article</code>, <code>orderItems</code></td>
</tr>
<tr>
<td><strong>Pivot Table</strong></td>
<td>Singular alphabetical (snake_case)</td>
<td><code>article_user</code>, <code>order_product</code></td>
<td><code>articles_users</code>, <code>user_article</code></td>
</tr>
<tr>
<td><strong>Table Column</strong></td>
<td>snake_case without model prefix</td>
<td><code>meta_title</code>, <code>status</code></td>
<td><code>MetaTitle</code>, <code>article_meta_title</code></td>
</tr>
<tr>
<td><strong>Model Property</strong></td>
<td>snake_case</td>
<td><code>$user-&gt;created_at</code></td>
<td><code>$user-&gt;createdAt</code></td>
</tr>
<tr>
<td><strong>Primary Key</strong></td>
<td>Default <code>id</code></td>
<td><code>id</code></td>
<td><code>article_id</code>, <code>custom_id</code></td>
</tr>
<tr>
<td><strong>Foreign Key</strong></td>
<td>Singular model + <code>_id</code></td>
<td><code>user_id</code>, <code>order_id</code></td>
<td><code>user_fk</code>, <code>id_user</code>, <code>users_id</code></td>
</tr>
<tr>
<td><strong>Relationship (<code>hasOne</code> / <code>belongsTo</code>)</strong></td>
<td>Singular (camelCase)</td>
<td><code>user()</code>, <code>articleComment()</code></td>
<td><code>users()</code>, <code>article_comment()</code></td>
</tr>
<tr>
<td><strong>Relationship (<code>hasMany</code> / <code>belongsToMany</code>)</strong></td>
<td>Plural (camelCase)</td>
<td><code>comments()</code>, <code>roles()</code></td>
<td><code>comment()</code>, <code>article_comments()</code></td>
</tr>
<tr>
<td><strong>Route URI</strong></td>
<td>Plural (kebab-case)</td>
<td><code>/articles/{article}</code></td>
<td><code>/article/{id}</code>, <code>/view_articles</code></td>
</tr>
<tr>
<td><strong>Route Name</strong></td>
<td>Plural resource dot notation</td>
<td><code>articles.index</code>, <code>orders.show</code></td>
<td><code>show_article</code>, <code>articleShow</code></td>
</tr>
<tr>
<td><strong>View Template</strong></td>
<td>kebab-case</td>
<td><code>show-filtered.blade.php</code></td>
<td><code>showFiltered.blade.php</code>, <code>show_filtered.blade.php</code></td>
</tr>
<tr>
<td><strong>Config File</strong></td>
<td>snake_case</td>
<td><code>config/google_calendar.php</code></td>
<td><code>googleCalendar.php</code>, <code>google-calendar.php</code></td>
</tr>
<tr>
<td><strong>Config Key</strong></td>
<td>snake_case</td>
<td><code>config(&#39;services.stripe_key&#39;)</code></td>
<td><code>config(&#39;services.StripeKey&#39;)</code></td>
</tr>
<tr>
<td><strong>Contract (Interface)</strong></td>
<td>Adjective or Noun (PascalCase)</td>
<td><code>Authenticatable</code>, <code>PaymentGateway</code></td>
<td><code>IAuthentication</code>, <code>AuthInterface</code></td>
</tr>
<tr>
<td><strong>Trait</strong></td>
<td>Adjective (PascalCase)</td>
<td><code>Notifiable</code>, <code>Sluggable</code></td>
<td><code>NotificationTrait</code>, <code>TraitNotifiable</code></td>
</tr>
<tr>
<td><strong>Enum</strong></td>
<td>Singular (PascalCase)</td>
<td><code>UserRole</code>, <code>OrderStatus</code></td>
<td><code>UserRoles</code>, <code>UserRoleEnum</code></td>
</tr>
<tr>
<td><strong>Form Request</strong></td>
<td>Singular Action + Model + <code>Request</code></td>
<td><code>UpdateUserRequest</code>, <code>StorePostRequest</code></td>
<td><code>UserFormRequest</code>, <code>UserRequest</code></td>
</tr>
<tr>
<td><strong>Seeder</strong></td>
<td>Singular (PascalCase)</td>
<td><code>UserSeeder</code>, <code>OrderSeeder</code></td>
<td><code>UsersSeeder</code>, <code>OrdersSeeder</code></td>
</tr>
<tr>
<td><strong>Resource Controller Action</strong></td>
<td>Standard REST verb</td>
<td><code>index</code>, <code>store</code>, <code>update</code></td>
<td><code>getAll</code>, <code>saveArticle</code></td>
</tr>
<tr>
<td><strong>Class Method</strong></td>
<td>camelCase</td>
<td><code>getFullName()</code>, <code>processPayment()</code></td>
<td><code>get_full_name()</code>, <code>ProcessPayment()</code></td>
</tr>
<tr>
<td><strong>Test Method</strong></td>
<td>camelCase or <code>test_</code> snake_case</td>
<td><code>testUserCanPurchase()</code>, <code>it_charges_order()</code></td>
<td><code>UserCanPurchase</code></td>
</tr>
<tr>
<td><strong>Collection Variable</strong></td>
<td>Descriptive plural</td>
<td><code>$activeUsers = User::active()-&gt;get()</code></td>
<td><code>$data</code>, <code>$active</code>, <code>$usersList</code></td>
</tr>
<tr>
<td><strong>Single Model Variable</strong></td>
<td>Descriptive singular</td>
<td><code>$user = User::first()</code></td>
<td><code>$users</code>, <code>$obj</code>, <code>$item</code></td>
</tr>
</tbody></table>
<h2>Why Conventions Matter</h2>
<ol>
<li><strong>Automatic Relation Resolution</strong>: Eloquent infers foreign keys and pivot tables automatically without requiring manual string parameters.</li>
<li><strong>Implicit Route Model Binding</strong>: Type-hinting <code>Article $article</code> on <code>/articles/{article}</code> resolves the model with zero controller boilerplate.</li>
<li><strong>Consistency</strong>: Engineers moving between Laravel projects immediately know where files live and how methods are named.</li>
</ol>
<h2>Summary</h2>
<ul>
<li>Eliminates redundant <code>$table</code>, <code>$primaryKey</code>, and relationship foreign key parameters.</li>
<li>Guarantees predictable codebase navigation across development teams.</li>
<li>Follows standard PHP-FIG PSR-12 and Laravel framework conventions.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Architecture</category>
            <category>Laravel</category>
            <category>Architecture</category>
            <category>Best Practices</category>
            <category>Clean Code</category>
        </item>
        <item>
            <title><![CDATA[Refactor PHP Enum Cases from snake_case to PascalCase with Rector]]></title>
            <link>https://mrpunyapal.dev/blog/php-rector-refactor-enums-pascal-case</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/php-rector-refactor-enums-pascal-case</guid>
            <pubDate>Mon, 20 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Automate renaming PHP 8.1+ enum cases from SNAKE_CASE to PascalCase across PHP classes and Blade templates using Rector and an Artisan refactoring script.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Automate renaming PHP 8.1+ enum cases from SNAKE_CASE to PascalCase across PHP classes and Blade templates using Rector and an Artisan refactoring script.</p>
</blockquote>
<p>When PHP 8.1 backed enums were introduced, many projects originally named enum cases in uppercase snake case (<code>case PENDING_REVIEW;</code>), matching older class constant conventions. Modern PHP and Laravel coding style standards (including PER-CS and Laravel Pint) recommend PascalCase for enum cases (<code>case PendingReview;</code>).</p>
<p>Renaming enum cases across a large production application manually is error-prone. By combining a custom Rector rule with an Artisan Blade updater, you can refactor all enum declarations, references, and Blade view usages automatically.</p>
<h2>1. Custom Rector Rule for Enum Cases</h2>
<pre><code class="language-php">namespace App\Rector;

use PhpParser\Node;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\EnumCase;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

final class RenameEnumCasesToPascalCaseRector extends AbstractRector
{
    public function __construct(
        private ReflectionProvider $reflectionProvider
    ) {}

    public function getNodeTypes(): array
    {
        return [EnumCase::class, ClassConstFetch::class];
    }

    public function refactor(Node $node): ?Node
    {
        if ($node instanceof EnumCase) {
            return $this-&gt;refactorEnumCase($node);
        }

        if ($node instanceof ClassConstFetch) {
            return $this-&gt;refactorEnumCaseFetch($node);
        }

        return null;
    }

    private function refactorEnumCase(EnumCase $node): ?Node
    {
        $oldName = $this-&gt;getName($node-&gt;name);
        $newName = $this-&gt;convertSnakeToPascalCase($oldName);

        if ($oldName !== $newName) {
            $node-&gt;name = new Identifier($newName);
            return $node;
        }

        return null;
    }

    private function refactorEnumCaseFetch(ClassConstFetch $node): ?Node
    {
        $objectType = $this-&gt;getType($node-&gt;class);

        if ($objectType-&gt;isEnum()-&gt;yes()) {
            $oldName = $this-&gt;getName($node-&gt;name);
            if ($oldName === &#039;class&#039; || $oldName === null) {
                return null;
            }

            $newName = $this-&gt;convertSnakeToPascalCase($oldName);
            if ($oldName !== $newName) {
                $node-&gt;name = new Identifier($newName);
                return $node;
            }
        }

        return null;
    }

    private function convertSnakeToPascalCase(string $name): string
    {
        return str_replace(&#039; &#039;, &#039;&#039;, ucwords(str_replace(&#039;_&#039;, &#039; &#039;, mb_strtolower($name))));
    }

    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition(&#039;Convert SNAKE_CASE enum cases to PascalCase&#039;, [
            new CodeSample(
                &#039;enum Status { case PENDING_PAYMENT; }&#039;,
                &#039;enum Status { case PendingPayment; }&#039;
            ),
        ]);
    }
}</code></pre><h2>2. Refactoring Blade Templates</h2>
<p>Because Rector parses PHP files rather than Blade templates, use an Artisan command to update enum case usages inside <code>resources/views/**/*.blade.php</code>:</p>
<pre><code class="language-php">namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

class RefactorBladeEnumsCommand extends Command
{
    protected $signature = &#039;refactor:blade-enums&#039;;
    protected $description = &#039;Refactor enum cases in Blade views from SNAKE_CASE to PascalCase&#039;;

    public function handle(): int
    {
        $views = File::allFiles(resource_path(&#039;views&#039;));

        foreach ($views as $file) {
            if ($file-&gt;getExtension() !== &#039;php&#039;) {
                continue;
            }

            $content = File::get($file-&gt;getRealPath());

            // Match patterns like Status::PENDING_PAYMENT
            $updated = preg_replace_callback(&#039;/([A-Z][A-Za-z0-9]+)::([A-Z0-9_]+)/&#039;, function ($matches) {
                $class = $matches[1];
                $case = $matches[2];

                if ($case === &#039;class&#039;) {
                    return $matches[0];
                }

                $pascalCase = str_replace(&#039; &#039;, &#039;&#039;, ucwords(str_replace(&#039;_&#039;, &#039; &#039;, strtolower($case))));
                return &quot;{$class}::{$pascalCase}&quot;;
            }, $content);

            if ($content !== $updated) {
                File::put($file-&gt;getRealPath(), $updated);
                $this-&gt;line(&quot;Updated view: &quot; . $file-&gt;getRelativePathname());
            }
        }

        $this-&gt;info(&#039;Blade enum refactoring completed.&#039;);
        return Command::SUCCESS;
    }
}</code></pre><h2>Summary</h2>
<ul>
<li>Use Rector to safely refactor Enum definitions and PHP references via AST inspection and PHPStan static reflection.</li>
<li>Run a targeted Blade regex migration script to update template references without manual search-and-replace.</li>
<li>Standardizes your enum architecture to PascalCase conventions across the entire application.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>PHP</category>
            <category>Tooling</category>
            <category>PHP</category>
            <category>Rector</category>
            <category>Enums</category>
            <category>Refactoring</category>
            <category>Blade</category>
            <category>Tooling</category>
        </item>
        <item>
            <title><![CDATA[Instantly Broadcast Real-Time Database Notifications Using Model Observers]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-broadcast-database-notifications-instantly</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-broadcast-database-notifications-instantly</guid>
            <pubDate>Sun, 11 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Automatically broadcast WebSocket events to private user channels whenever a database notification is saved using an observer on Laravel's DatabaseNotification model.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Automatically broadcast WebSocket events to private user channels whenever a database notification is saved using an observer on Laravel&#39;s DatabaseNotification model.</p>
</blockquote>
<p>Laravel&#39;s notification system supports both database persistence (<code>via: [&#39;database&#39;]</code>) and real-time broadcasting (<code>via: [&#39;broadcast&#39;]</code>). However, configuring notifications to broadcast simultaneously often duplicates payload formatting and requires configuring separate broadcast channels for each notification class.</p>
<p>By attaching an observer to Laravel&#39;s underlying <code>DatabaseNotification</code> model, you can automatically broadcast every database notification in real time over private channels with zero per-notification boilerplate.</p>
<h2>1. Register the Observer in AppServiceProvider</h2>
<pre><code class="language-php">namespace App\Providers;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        DatabaseNotification::observe(new class {
            public function created(DatabaseNotification $notification): void
            {
                broadcast(new class($notification) {
                    public function __construct(
                        public DatabaseNotification $notification
                    ) {}

                    public function broadcastOn(): PrivateChannel
                    {
                        // Derives channel from notifiable type (e.g. &#039;users.42&#039;)
                        $type = strtolower(class_basename(str($this-&gt;notification-&gt;notifiable_type)-&gt;plural()));
                        return new PrivateChannel(&quot;{$type}.{$this-&gt;notification-&gt;notifiable_id}&quot;);
                    }

                    public function broadcastAs(): string
                    {
                        return &#039;DatabaseNotificationCreated&#039;;
                    }
                });
            }
        });
    }
}</code></pre><h2>2. Listening with Laravel Echo (Frontend)</h2>
<p>In your client-side JavaScript or Alpine.js component, subscribe to the user&#39;s private channel:</p>
<pre><code class="language-javascript">Echo.private(`users.${userId}`)
    .listen(&#039;.DatabaseNotificationCreated&#039;, (e) =&gt; {
        console.log(&#039;New notification received:&#039;, e.notification);
    });</code></pre><h2>3. Listening in Livewire Components</h2>
<p>In Livewire 3 components, use the <code>#[On]</code> attribute:</p>
<pre><code class="language-php">namespace App\Livewire;

use Livewire\Attributes\On;
use Livewire\Component;

class NotificationBell extends Component
{
    #[On(&#039;echo-private:users.{userId},.DatabaseNotificationCreated&#039;)]
    public function onNotificationReceived(array $payload): void
    {
        $this-&gt;dispatch(&#039;refreshNotifications&#039;);
    }
}</code></pre><h2>Summary</h2>
<ul>
<li>Observe <code>DatabaseNotification::created</code> to automatically broadcast real-time events for every stored database notification.</li>
<li>Eliminates the need to duplicate broadcast payload definitions in individual notification classes.</li>
<li>Directly integrates with Laravel Echo, Livewire, and Vue/React frontend architectures.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Events</category>
            <category>Laravel</category>
            <category>Notifications</category>
            <category>WebSockets</category>
            <category>Events</category>
            <category>Real-Time</category>
        </item>
        <item>
            <title><![CDATA[Cast Enums Inside JSON and Array Column Keys in Eloquent]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-cast-enum-json-array-key</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-cast-enum-json-array-key</guid>
            <pubDate>Tue, 30 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Cast enum instances nested within JSON or array database columns using Eloquent Attribute accessors and mutators.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Cast enum instances nested within JSON or array database columns using Eloquent Attribute accessors and mutators.</p>
</blockquote>
<p>Laravel provides first-party enum casting for standard model columns:</p>
<pre><code class="language-php">protected $casts = [
    &#039;status&#039; =&gt; PostStatus::class,
];</code></pre><p>However, when an enum value is stored inside a nested JSON object or array column (such as <code>options-&gt;status</code> or <code>settings[&#39;status&#39;]</code>), Laravel&#39;s default column casting casts the entire column to an array, leaving the nested enum value as a raw string or integer.</p>
<p>Using Eloquent&#39;s <code>Attribute</code> accessor and mutator, you can automatically hydrate and serialize nested enum instances.</p>
<h2>Implementing the Nested Enum Cast</h2>
<pre><code class="language-php">namespace App\Models;

use App\Enums\PostStatus;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected function options(): Attribute
    {
        return Attribute::make(
            get: function ($value): array {
                $options = json_decode($value ?? &#039;[]&#039;, true) ?? [];

                // Hydrate string value to Backed Enum instance
                if (isset($options[&#039;status&#039;])) {
                    $options[&#039;status&#039;] = PostStatus::tryFrom($options[&#039;status&#039;]) ?? $options[&#039;status&#039;];
                }

                return $options;
            },
            set: function ($value): string {
                $options = is_array($value) ? $value : (json_decode($value, true) ?? []);

                // Serialize Backed Enum instance to scalar value before storing
                if (isset($options[&#039;status&#039;]) &amp;&amp; $options[&#039;status&#039;] instanceof PostStatus) {
                    $options[&#039;status&#039;] = $options[&#039;status&#039;]-&gt;value;
                }

                return json_encode($options);
            }
        );
    }
}</code></pre><h2>Practical Usage</h2>
<p>You can now interact with nested enum properties as strongly typed objects:</p>
<pre><code class="language-php">$post = Post::find(1);

// Accessing the nested enum
if ($post-&gt;options[&#039;status&#039;] === PostStatus::Draft) {
    // Strongly typed enum comparison
}

// Updating the nested enum
$options = $post-&gt;options;
$options[&#039;status&#039;] = PostStatus::Published;
$post-&gt;options = $options;
$post-&gt;save();</code></pre><h2>Summary</h2>
<ul>
<li>Use Eloquent <code>Attribute::make()</code> to hydrate and serialize enums nested inside JSON attributes.</li>
<li>Use <code>PostStatus::tryFrom()</code> in the getter to gracefully handle unexpected values without throwing exceptions.</li>
<li>Ensures strongly typed enum consistency even for schemaless or flexible JSON payloads.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Eloquent</category>
            <category>Laravel</category>
            <category>Eloquent</category>
            <category>Enums</category>
            <category>Casts</category>
            <category>JSON</category>
        </item>
        <item>
            <title><![CDATA[Clean Up HTML Forms with Blade's @checked and @selected Directives]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-blade-checked-and-selected-directives</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-blade-checked-and-selected-directives</guid>
            <pubDate>Wed, 24 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Replace cumbersome ternary operators in HTML form inputs with Laravel's @checked and @selected Blade directives.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Replace cumbersome ternary operators in HTML form inputs with Laravel&#39;s @checked and @selected Blade directives.</p>
</blockquote>
<p>Conditionally adding <code>checked</code> or <code>selected</code> attributes to checkboxes, radio buttons, and select dropdowns usually requires repetitive inline PHP or ternary expressions:</p>
<pre><code class="language-html">&lt;!-- Old verbose syntax --&gt;
&lt;input type=&quot;checkbox&quot; name=&quot;active&quot; value=&quot;1&quot; {{ old(&#039;active&#039;, $user-&gt;is_active) ? &#039;checked&#039; : &#039;&#039; }}&gt;

&lt;option value=&quot;pro&quot; {{ old(&#039;plan&#039;, $user-&gt;plan) === &#039;pro&#039; ? &#039;selected&#039; : &#039;&#039; }}&gt;Pro&lt;/option&gt;</code></pre><p>Laravel provides <code>@checked</code> and <code>@selected</code> to express this cleanly.</p>
<h2>Using @checked for Checkboxes and Radio Buttons</h2>
<pre><code class="language-blade">&lt;input 
    type=&quot;checkbox&quot; 
    name=&quot;newsletter&quot; 
    value=&quot;1&quot; 
    @checked(old(&#039;newsletter&#039;, $user-&gt;subscribed_to_newsletter)) 
/&gt;</code></pre><p>If the evaluated expression is truthy, Laravel outputs <code>checked=&quot;checked&quot;</code>. If falsy, nothing is rendered.</p>
<h2>Using @selected for Dropdown Options</h2>
<pre><code class="language-blade">&lt;select name=&quot;status&quot;&gt;
    &lt;option value=&quot;draft&quot; @selected(old(&#039;status&#039;, $post-&gt;status) === &#039;draft&#039;)&gt;Draft&lt;/option&gt;
    &lt;option value=&quot;published&quot; @selected(old(&#039;status&#039;, $post-&gt;status) === &#039;published&#039;)&gt;Published&lt;/option&gt;
    &lt;option value=&quot;archived&quot; @selected(old(&#039;status&#039;, $post-&gt;status) === &#039;archived&#039;)&gt;Archived&lt;/option&gt;
&lt;/select&gt;</code></pre><h2>Also: @disabled and @readonly</h2>
<p>Laravel also provides <code>@disabled</code> and <code>@readonly</code> directives:</p>
<pre><code class="language-blade">&lt;button type=&quot;submit&quot; @disabled($cart-&gt;isEmpty())&gt;
    Checkout
&lt;/button&gt;</code></pre><h2>Summary</h2>
<ul>
<li>Eliminates verbose ternary operators in HTML templates.</li>
<li>Automatically handles boolean truth-testing and outputs appropriate HTML attributes.</li>
<li>Works across checkboxes (<code>@checked</code>), select menus (<code>@selected</code>), and buttons (<code>@disabled</code>).</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Blade</category>
            <category>Laravel</category>
            <category>Blade</category>
            <category>HTML</category>
            <category>Clean Code</category>
        </item>
        <item>
            <title><![CDATA[Human-Friendly Enum Case Labels with a Reusable HasLabel Trait]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-enum-has-label-trait</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-enum-has-label-trait</guid>
            <pubDate>Wed, 17 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Add human-readable display labels to PHP 8.1+ enums automatically using a lightweight HasLabel trait without maintaining manual match statements.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Add human-readable display labels to PHP 8.1+ enums automatically using a lightweight HasLabel trait without maintaining manual match statements.</p>
</blockquote>
<p>When using backed Enums in Laravel models and Blade views, you often need a formatted, user-friendly label (such as displaying <code>&quot;Partially Paid and Refunded&quot;</code> for <code>PaymentStatus::PartiallyPaidAndRefunded</code>).</p>
<p>Maintaining manual <code>match ($this)</code> methods across dozens of application enums is repetitive. A lightweight trait can derive clean titles automatically while allowing custom overrides when needed.</p>
<h2>The HasLabel Trait</h2>
<pre><code class="language-php">namespace App\Traits;

trait HasLabel
{
    public function label(): string
    {
        // Convert case name (e.g. PARTIALLY_REFUNDED or PartiallyRefunded) to Title Case
        $clean = str_replace(&#039;_&#039;, &#039; &#039;, $this-&gt;name);

        // Convert camelCase / PascalCase boundaries to spaced words
        $spaced = preg_replace(&#039;/(?&lt;!^)[A-Z]/&#039;, &#039; $0&#039;, $clean);

        return ucwords(strtolower(trim($spaced)));
    }
}</code></pre><h2>Applying the Trait to Enums</h2>
<pre><code class="language-php">namespace App\Enums;

use App\Traits\HasLabel;

enum PaymentStatus: int
{
    use HasLabel;

    case PENDING = 1;
    case FAILED = 2;
    case REFUNDED = 3;
    case PARTIALLY_REFUNDED = 4;
    case PARTIALLY_PAID = 5;
    case PAID = 6;
    case PARTIALLY_PAID_AND_REFUNDED = 7;
}</code></pre><h2>Usage in Controllers and Blade Views</h2>
<pre><code class="language-php">use App\Enums\PaymentStatus;

PaymentStatus::PENDING-&gt;label();
// Output: &quot;Pending&quot;

PaymentStatus::PARTIALLY_REFUNDED-&gt;label();
// Output: &quot;Partially Refunded&quot;

PaymentStatus::PARTIALLY_PAID_AND_REFUNDED-&gt;label();
// Output: &quot;Partially Paid And Refunded&quot;</code></pre><p>In Blade templates:</p>
<pre><code class="language-blade">&lt;span class=&quot;badge&quot;&gt;
    {{ $order-&gt;payment_status-&gt;label() }}
&lt;/span&gt;</code></pre><h2>Overriding Specific Labels</h2>
<p>If a specific case requires specialized branding or wording that differs from the automated title, override the method:</p>
<pre><code class="language-php">enum InvoiceStatus: string
{
    use HasLabel;

    case DRAFT = &#039;draft&#039;;
    case SENT = &#039;sent&#039;;
    case OVERDUE = &#039;overdue&#039;;

    public function label(): string
    {
        return match ($this) {
            self::OVERDUE =&gt; &#039;Action Required: Overdue&#039;,
            default =&gt; $this-&gt;defaultLabel(),
        };
    }
}</code></pre><h2>Summary</h2>
<ul>
<li>Use a <code>HasLabel</code> trait to generate consistent, human-friendly titles from enum case names automatically.</li>
<li>Eliminates boilerplate <code>match</code> expressions across project enums.</li>
<li>Easily customizable for specific localized or marketing terminology when needed.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Eloquent</category>
            <category>Laravel</category>
            <category>Enums</category>
            <category>Architecture</category>
            <category>Clean Code</category>
        </item>
        <item>
            <title><![CDATA[Fix Blade View Cache Compilation for Teleport Directives in Livewire]]></title>
            <link>https://mrpunyapal.dev/blog/livewire-teleport-blade-view-cache-directive-fix</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/livewire-teleport-blade-view-cache-directive-fix</guid>
            <pubDate>Sun, 14 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Prevent view compilation glitches when running php artisan view:cache on templates using @teleport by registering explicit Blade directives.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Prevent view compilation glitches when running php artisan view:cache on templates using @teleport by registering explicit Blade directives.</p>
</blockquote>
<p>When using Livewire or Alpine.js <code>@teleport</code> directives inside Blade templates, running <code>php artisan view:cache</code> in production or CI/CD pipelines can occasionally cause compilation errors or unparsed tags if custom directive compilation is missing from the service container.</p>
<p>You can ensure consistent, error-free view compilation by registering custom Blade compiler directives for <code>@teleport</code> and <code>@endteleport</code>.</p>
<h2>The Fix in AppServiceProvider</h2>
<p>Add the custom directive registration inside your <code>AppServiceProvider::register()</code> or <code>boot()</code> method:</p>
<pre><code class="language-php">namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Blade::directive(&#039;teleport&#039;, function (string $expression): string {
            return &quot;&lt;template x-teleport=&quot;&lt;?php echo e({$expression}); ?&gt;&quot;&gt;&quot;;
        });

        Blade::directive(&#039;endteleport&#039;, function (): string {
            return &quot;&lt;/template&gt;&quot;;
        });
    }
}</code></pre><h2>Usage in Blade Views</h2>
<p>You can now use <code>@teleport</code> cleanly across cached views:</p>
<pre><code class="language-blade">&lt;div x-data=&quot;{ open: false }&quot;&gt;
    &lt;button @click=&quot;open = true&quot;&gt;Open Modal&lt;/button&gt;

    @teleport(&#039;body&#039;)
        &lt;div x-show=&quot;open&quot; class=&quot;modal-backdrop&quot;&gt;
            &lt;div class=&quot;modal-content&quot;&gt;
                &lt;h3&gt;Modal Title&lt;/h3&gt;
                &lt;button @click=&quot;open = false&quot;&gt;Close&lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    @endteleport
&lt;/div&gt;</code></pre><h2>Testing View Caching</h2>
<p>Run view cache compilation to verify:</p>
<pre><code class="language-bash">php artisan view:clear
php artisan view:cache</code></pre><p>All views compile cleanly into cached PHP templates without syntax exceptions.</p>
<h2>Summary</h2>
<ul>
<li>Register explicit <code>@teleport</code> and <code>@endteleport</code> Blade directives in <code>AppServiceProvider</code> to resolve compilation glitches during <code>view:cache</code>.</li>
<li>Translates cleanly into standard Alpine.js <code>&lt;template x-teleport=&quot;...&quot;&gt;</code> elements.</li>
<li>Prevents production deployment build failures when caching views.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Livewire</category>
            <category>Components</category>
            <category>Laravel</category>
            <category>Livewire</category>
            <category>Blade</category>
            <category>Alpine.js</category>
            <category>Tooling</category>
        </item>
        <item>
            <title><![CDATA[Centralized Application Activity Logging with Laravel Event Subscribers]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-activity-log-event-subscriber</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-activity-log-event-subscriber</guid>
            <pubDate>Fri, 01 Dec 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Capture HTTP requests, Artisan CLI commands, and outgoing API responses into a unified audit trail using a single Laravel Event Subscriber.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Capture HTTP requests, Artisan CLI commands, and outgoing API responses into a unified audit trail using a single Laravel Event Subscriber.</p>
</blockquote>
<p>Applications frequently need to log key lifecycle actions for debugging and compliance. Instead of scattering logging logic across various middleware, commands, and service classes, Laravel Event Subscribers let you listen to multiple framework events from a single centralized class.</p>
<h2>The ActivityLogEventSubscriber</h2>
<pre><code class="language-php">namespace App\Listeners;

use Illuminate\Console\Events\CommandFinished;
use Illuminate\Events\Dispatcher;
use Illuminate\Foundation\Http\Events\RequestHandled;
use Illuminate\Http\Client\Events\ResponseReceived;
use Illuminate\Support\Facades\Log;

class ActivityLogEventSubscriber
{
    public function handleHttpRequest(RequestHandled $event): void
    {
        $this-&gt;logActivity(&#039;RequestHandled&#039;, [
            &#039;method&#039; =&gt; $event-&gt;request-&gt;method(),
            &#039;url&#039; =&gt; $event-&gt;request-&gt;fullUrl(),
            &#039;status&#039; =&gt; $event-&gt;response-&gt;getStatusCode(),
            &#039;user_id&#039; =&gt; $event-&gt;request-&gt;user()?-&gt;id,
        ]);
    }

    public function handleConsoleCommand(CommandFinished $event): void
    {
        $this-&gt;logActivity(&#039;CommandFinished&#039;, [
            &#039;command&#039; =&gt; $event-&gt;command,
            &#039;exit_code&#039; =&gt; $event-&gt;exitCode,
        ]);
    }

    public function handleHttpClientResponse(ResponseReceived $event): void
    {
        $this-&gt;logActivity(&#039;HttpClientResponse&#039;, [
            &#039;url&#039; =&gt; (string) $event-&gt;request-&gt;url(),
            &#039;status&#039; =&gt; $event-&gt;response-&gt;status(),
        ]);
    }

    private function logActivity(string $eventType, array $payload): void
    {
        Log::channel(&#039;activity&#039;)-&gt;info(&quot;Activity [{$eventType}]&quot;, $payload);
    }

    public function subscribe(Dispatcher $events): array
    {
        return [
            RequestHandled::class =&gt; &#039;handleHttpRequest&#039;,
            CommandFinished::class =&gt; &#039;handleConsoleCommand&#039;,
            ResponseReceived::class =&gt; &#039;handleHttpClientResponse&#039;,
        ];
    }
}</code></pre><h2>Registering the Subscriber</h2>
<p>In Laravel 11+, register the subscriber inside your <code>AppServiceProvider::boot()</code> method (or <code>EventServiceProvider</code> in earlier versions):</p>
<pre><code class="language-php">namespace App\Providers;

use App\Listeners\ActivityLogEventSubscriber;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::subscribe(ActivityLogEventSubscriber::class);
    }
}</code></pre><h2>Summary</h2>
<ul>
<li>Use an Event Subscriber to group related event handlers into a single cohesive class.</li>
<li>Listen to built-in framework events like <code>RequestHandled</code>, <code>CommandFinished</code>, and <code>ResponseReceived</code> for comprehensive audit logging.</li>
<li>Decouples monitoring and logging logic from core HTTP controllers and console commands.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Events</category>
            <category>Laravel</category>
            <category>Events</category>
            <category>Logging</category>
            <category>Architecture</category>
            <category>Audit</category>
        </item>
        <item>
            <title><![CDATA[Multi-Tenant Team Scoping with a Reusable BelongsToTeam Model Trait]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-belongs-to-team-multitenancy-trait</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-belongs-to-team-multitenancy-trait</guid>
            <pubDate>Sun, 05 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Automatically assign tenant IDs and restrict Eloquent queries using a reusable BelongsToTeam model trait with model booting and global scopes.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Automatically assign tenant IDs and restrict Eloquent queries using a reusable BelongsToTeam model trait with model booting and global scopes.</p>
</blockquote>
<p>In multi-tenant or team-based SaaS applications, models (such as projects, invoices, and documents) must belong to a specific team, and users must only access records belonging to their active team.</p>
<p>Instead of writing repetitive query scopes and <code>$model-&gt;team_id = auth()-&gt;user()-&gt;team_id</code> assignments in every controller, you can encapsulate multi-tenant behavior in a reusable model trait.</p>
<h2>The BelongsToTeam Trait</h2>
<pre><code class="language-php">namespace App\Traits\Models;

use App\Models\Team;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

trait BelongsToTeam
{
    public static function bootBelongsToTeam(): void
    {
        // Automatically set the team_id when creating a new record
        static::creating(function ($model): void {
            if (auth()-&gt;check() &amp;&amp; empty($model-&gt;team_id)) {
                $model-&gt;team_id = auth()-&gt;user()-&gt;team_id;
            }
        });
    }

    protected static function booted(): void
    {
        parent::booted();

        // Apply a global scope to filter queries by active team
        if (auth()-&gt;check()) {
            static::addGlobalScope(&#039;team&#039;, function (Builder $query): void {
                $query-&gt;team();
            });
        }
    }

    public function scopeTeam(Builder $query): Builder
    {
        return $query-&gt;when(
            auth()-&gt;user()?-&gt;team_id,
            function (Builder $query, int $teamId): Builder {
                // Prefix column with table name to prevent SQL ambiguity in joins
                return $query-&gt;where($this-&gt;getTable() . &#039;.team_id&#039;, $teamId);
            },
            function (Builder $query): Builder {
                // If the user has no team, prevent data leakage
                abort(403, &#039;User does not belong to an active team.&#039;);
            }
        );
    }

    public function team(): BelongsTo
    {
        return $this-&gt;belongsTo(Team::class);
    }
}</code></pre><h2>Using the Trait on Models</h2>
<pre><code class="language-php">namespace App\Models;

use App\Traits\Models\BelongsToTeam;
use Illuminate\Database\Eloquent\Model;

class Project extends Model
{
    use BelongsToTeam;

    protected $fillable = [&#039;name&#039;, &#039;description&#039;];
}</code></pre><h2>What This Automates</h2>
<ol>
<li><strong>Automatic Assignment</strong>: Calling <code>Project::create([&#39;name&#39; =&gt; &#39;API v2&#39;])</code> automatically populates <code>team_id</code> from the authenticated user.</li>
<li><strong>Automatic Query Scoping</strong>: Calling <code>Project::all()</code> automatically generates <code>WHERE projects.team_id = ?</code>.</li>
<li><strong>Join Safety</strong>: Using <code>$this-&gt;getTable() . &#39;.team_id&#39;</code> ensures joins and <code>hasManyThrough</code> relationships do not encounter &quot;ambiguous column&quot; SQL errors.</li>
</ol>
<h2>Summary</h2>
<ul>
<li>Use a <code>BelongsToTeam</code> trait to automate tenant ID assignment on creation and filter queries via global scopes.</li>
<li>Table-prefix column references in query scopes to prevent SQL errors in joins.</li>
<li>Eliminates manual scoping across controllers and guarantees tenant isolation.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Architecture</category>
            <category>Laravel</category>
            <category>Eloquent</category>
            <category>Multi-Tenancy</category>
            <category>Architecture</category>
            <category>Security</category>
        </item>
        <item>
            <title><![CDATA[Reusable whereLike Macro with Relationship and Expression Support]]></title>
            <link>https://mrpunyapal.dev/blog/laravel-custom-wherelike-macro</link>
            <guid isPermaLink="true">https://mrpunyapal.dev/blog/laravel-custom-wherelike-macro</guid>
            <pubDate>Fri, 27 Oct 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Simplify multi-column wildcard searches across model columns, relationships, and raw SQL expressions using a powerful whereLike macro on the Eloquent Builder.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>Simplify multi-column wildcard searches across model columns, relationships, and raw SQL expressions using a powerful whereLike macro on the Eloquent Builder.</p>
</blockquote>
<p>Searching across multiple attributes frequently leads to verbose and repetitive <code>orWhere</code> query chains in controller code.</p>
<p>By registering a custom <code>whereLike</code> macro on Eloquent&#39;s <code>Builder</code>, you can search across model columns, dot-notation relationships (<code>user.name</code>), and custom database expressions (<code>DB::raw()</code>) in a single readable call.</p>
<h2>Registering the Macro in AppServiceProvider</h2>
<pre><code class="language-php">namespace App\Providers;

use Illuminate\Contracts\Database\Query\Expression;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Builder::macro(&#039;whereLike&#039;, function ($attributes, string $searchTerm) {
            return $this-&gt;where(function (Builder $query) use ($attributes, $searchTerm) {
                foreach (Arr::wrap($attributes) as $attribute) {
                    $query-&gt;when(
                        // Check if attribute is a relationship dot-notation string (e.g. &#039;user.name&#039;)
                        ! ($attribute instanceof Expression) &amp;&amp; str_contains((string) $attribute, &#039;.&#039;),
                        function (Builder $query) use ($attribute, $searchTerm) {
                            [$relation, $relatedAttribute] = explode(&#039;.&#039;, (string) $attribute);

                            $query-&gt;orWhereHas($relation, function (Builder $query) use ($relatedAttribute, $searchTerm) {
                                $query-&gt;where($relatedAttribute, &#039;LIKE&#039;, &quot;%{$searchTerm}%&quot;);
                            });
                        },
                        function (Builder $query) use ($attribute, $searchTerm) {
                            // Search on local column or DB::raw expression
                            $query-&gt;orWhere($attribute, &#039;LIKE&#039;, &quot;%{$searchTerm}%&quot;);
                        }
                    );
                }
            });
        });
    }
}</code></pre><h2>Usage Example</h2>
<p>You can pass single columns, related model fields using dot notation, and formatted raw SQL expressions:</p>
<pre><code class="language-php">use App\Models\Post;
use Illuminate\Support\Facades\DB;

$search = request(&#039;search&#039;);

$posts = Post::query()
    -&gt;whereLike([
        &#039;title&#039;,
        &#039;description&#039;,
        &#039;user.name&#039;,
        &#039;user.email&#039;,
        DB::raw(&#039;CONCAT(user.first_name, &quot; &quot;, user.last_name)&#039;),
        DB::raw(&#039;DATE_FORMAT(created_at, &quot;%d/%m/%Y&quot;)&#039;),
    ], $search)
    -&gt;with(&#039;user&#039;)
    -&gt;paginate(15);</code></pre><h2>How It Works</h2>
<ol>
<li><strong>Encapsulated Scope</strong>: Wraps the entire search clause in a single <code>$this-&gt;where(function ($query) ...)</code> closure so that boolean <code>AND</code> / <code>OR</code> operator precedence is respected when chained with other query filters.</li>
<li><strong>Relationship Detection</strong>: Automatically detects dot notation (<code>&#39;user.name&#39;</code>) and applies <code>orWhereHas</code> subqueries against the related model.</li>
<li><strong>Expression Compatibility</strong>: Supports <code>DB::raw()</code> expressions without throwing type errors, enabling searches on computed columns and SQL date formats.</li>
</ol>
<h2>Summary</h2>
<ul>
<li>Use <code>Builder::macro(&#39;whereLike&#39;)</code> to consolidate multi-attribute wildcard searches into a single expressive method.</li>
<li>Automatically handles both local table columns and related model attributes via <code>orWhereHas</code>.</li>
<li>Respects SQL operator grouping and supports raw database expressions.</li>
</ul>
]]></content:encoded>
            <author>contact@mrpunyapal.dev (Punyapal Shah)</author>
            <category>Laravel</category>
            <category>Eloquent</category>
            <category>Laravel</category>
            <category>Eloquent</category>
            <category>Macros</category>
            <category>Search</category>
            <category>Database</category>
        </item>
    </channel>
</rss>
