Laravel Magazine
Laravel Tips for Writing More Expressive Code

Laravel Tips for Writing More Expressive Code

Eric Van Johnson ·

The best Laravel code reads like a description of what you want to happen, not a set of instructions for how to make it happen. Here are eight patterns that move you in that direction.

1. Use value() Instead of a Temporary Variable

When you need to transform something inline without introducing a named variable, reach for value():

// Before
$tax = config('shop.tax_rate');
$total = $subtotal * (1 + $tax);

// After
$total = $subtotal * (1 + value(config('shop.tax_rate')));

// Or with a closure for more complex logic
$discount = value(function () use ($user) {
    return $user->isVip() ? 0.20 : 0.00;
});

It's especially useful for wrapping logic you want to evaluate lazily in a readable way.

2. Tap Into Fluent Chains With tap()

tap() lets you peek at a value mid-chain without breaking the chain:

$order = tap(Order::create($data), function ($order) {
    $order->items()->createMany($items);
    event(new OrderPlaced($order));
});

The return value of tap() is always the first argument, not the closure's return value. That means you can fire off side effects and keep the readable fluent style.

3. Conditional Query Clauses With when()

Stop wrapping query conditions in if blocks:

// Before
$query = Post::query();
if ($request->filled('status')) {
    $query->where('status', $request->status);
}
if ($request->filled('author')) {
    $query->where('user_id', $request->author);
}

// After
Post::query()
    ->when($request->filled('status'), fn ($q) => $q->where('status', $request->status))
    ->when($request->filled('author'), fn ($q) => $q->where('user_id', $request->author))
    ->get();

when() also accepts a third argument — the "else" branch — if you need to apply a different scope when the condition is false.

4. Use firstOrNew() to Avoid Redundant Lookups

Instead of fetching a model and then checking if it exists, let Eloquent handle it:

// Before
$profile = UserProfile::where('user_id', $userId)->first();
if (!$profile) {
    $profile = new UserProfile(['user_id' => $userId]);
}
$profile->bio = $bio;
$profile->save();

// After
$profile = UserProfile::firstOrNew(['user_id' => $userId]);
$profile->bio = $bio;
$profile->save();

firstOrNew() finds the record or returns a new unsaved instance. firstOrCreate() goes one step further and immediately saves the new record if it doesn't exist.

5. Higher-Order Messages on Collections

Collections support higher-order messages, which let you call methods on collection items without writing explicit closures:

// Before
$names = $users->map(fn ($user) => $user->name);
$admins = $users->filter(fn ($user) => $user->isAdmin());

// After
$names = $users->map->name;
$admins = $users->filter->isAdmin();

This works for properties, accessors, and zero-argument methods. It's concise and reads naturally.

6. Reach for rescue() for Non-Critical Operations

When you need to try something and fall back gracefully without a full try-catch block:

$avatar = rescue(
    fn () => $this->fetchAvatarFromRemote($user),
    fn ($e) => asset('images/default-avatar.png'),
    report: true
);

The third argument controls whether exceptions get reported to your exception handler. Set it to false for truly expected failures you don't need to track.

7. Use str() for Fluent String Manipulation

The global str() helper gives you a fluent Stringable instance:

$slug = str($post->title)
    ->lower()
    ->replace(' ', '-')
    ->replaceMatches('/[^a-z0-9\-]/', '')
    ->limit(60, '');

This is far more readable than chaining Str:: static calls, and it composes naturally because each method returns the same Stringable instance.

8. Use collect() on Anything

You don't need an array of Eloquent models to use collection methods. collect() works on any iterable — JSON responses, CSV rows, API payloads:

$response = Http::get('https://api.example.com/products');

$names = collect($response->json('data'))
    ->where('active', true)
    ->sortBy('name')
    ->pluck('name')
    ->values();

If you find yourself writing nested foreach loops with conditional logic inside, that's usually a sign a collection pipeline would read better.

Stay Updated

Subscribe to our newsletter

Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.

Weekly articles
We send a new issue of the newsletter every week on Friday.
No spam
We'll never share your email address and you can opt out at any time.