Category: Laravel

  • Branch From the Right Base When Stacking PRs

    Branch From the Right Base When Stacking PRs

    When you have a feature that spans multiple PRs — say, PR #1 builds the core, PR #2 adds an enhancement on top — you need to stack them properly.

    The mistake I see developers make: they branch PR #2 from master instead of from PR #1’s branch.

    # ❌ Wrong — branches from master, will conflict with PR #1
    git checkout master
    git checkout -b feature/enhancement
    
    # ✅ Right — branches from PR #1, builds on top of it
    git checkout feature/core-feature
    git checkout -b feature/enhancement

    And the PR target matters too:

    • PR #1 targets master (or main)
    • PR #2 targets feature/core-feature (PR #1’s branch), not master

    Once PR #1 is merged, you update PR #2’s target to master. GitHub and GitLab both handle this cleanly — the diff will shrink to just PR #2’s changes.

    When to combine instead: If the PRs are small enough and touch the same files, sometimes it’s simpler to combine them into one branch. We do this when features are tightly coupled and reviewing them separately would lose context.

    # Combining two feature branches into one
    git checkout master
    git checkout -b feature/combined
    git merge feature/part-1 --no-ff
    git merge feature/part-2 --no-ff

    The goal is always the same: make the reviewer’s job easy. Small, focused PRs with clear lineage beat a single massive PR every time.

  • Make Your Artisan Commands Idempotent

    Make Your Artisan Commands Idempotent

    We recently had to bulk-process a batch of records — update statuses, add notes, trigger some side effects. The kind of thing you write a one-off artisan command for.

    The first instinct is to just loop and execute. But what happens when the command fails halfway through? Or the queue worker restarts? You need to run it again, and now half your records get double-processed.

    The fix: Make every step check if it’s already been done.

    public function handle()
    {
        $orders = Order::whereIn('reference', $this->references)->get();
    
        foreach ($orders as $order) {
            // Step 1: Always safe to repeat
            $order->addNote('Bulk processed on ' . now()->toDateString());
    
            // Step 2: Skip if already done
            if ($order->status === OrderStatus::CANCELLED) {
                $this->info("Skipping {$order->reference} — already cancelled");
                continue;
            }
    
            // Step 3: Do the actual work
            $order->cancel();
            $this->info("Cancelled {$order->reference}");
        }
    }

    Key patterns:

    • Check before acting — If the record is already in the target state, skip it
    • Log what you skip — So you can verify the second run did nothing harmful
    • Add a --dry-run flag — Always. Test the logic before committing to production changes
    $this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Run without making changes');
    
    // In your handle method:
    if ($this->option('dry-run')) {
        $this->info("[DRY RUN] Would cancel {$order->reference}");
        continue;
    }

    The rule is simple: if you can’t safely run it twice, it’s not ready for production.

  • Use toRawSql() to See What Eloquent Actually Runs

    Use toRawSql() to See What Eloquent Actually Runs

    When you’re debugging a complex Eloquent query, toSql() gives you the query with ? placeholders. Not super helpful when you need to paste it into your database client.

    Since Laravel 10.15, you can use toRawSql() instead:

    // Before — placeholders, not useful for debugging
    $query->toSql();
    // SELECT * FROM users WHERE status = ? AND role = ?
    
    // After — actual values inline
    $query->toRawSql();
    // SELECT * FROM users WHERE status = 'active' AND role = 'admin'

    Works on both the Eloquent builder and the base query builder:

    // Eloquent builder
    User::where('status', 'active')->toRawSql();
    
    // Base query builder (useful for complex joins)
    User::where('status', 'active')->toBase()->toRawSql();

    I use ->toBase()->toRawSql() constantly when comparing old vs new query approaches during refactors. Paste both into your DB client, compare the execution plans, and you know exactly what changed.

    Bonus: If you’re on an older Laravel version, the query log approach still works:

    DB::enableQueryLog();
    // ...run your query...
    dd(DB::getQueryLog());

    But toRawSql() is cleaner when you just need one query.