Simplifying Laravel Eloquent Scopes: Using a Single Method for Single and Multiple Values

1 min read
tree through a magnifying glass.

Photo by Nicolas Houdayer on Unsplash

When working with Laravel, you often need to filter query results based on a single value or a list of values. Instead of writing separate methods for each case, there's a simple trick that lets you handle both scenarios in a clean and efficient way. Let's dive in!

The Common Approach: Separate Methods for Single and Multiple Values

You might start by creating two separate scope methods to handle these cases:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
 
class User extends Model
{
/**
* Scope a query to only include users of a given type.
*/
public function scopeOfType(Builder $query, string $type): void
{
$query->where('type', $type);
}
 
/**
* Scope a query to only include users of a given types.
*
* @param string[] $types
*/
public function scopeOfTypes(Builder $query, array $types): void
{
$query->whereIn('type', $types);
}
}

This works perfectly well, but it feels repetitive. Both perform similar filtering by user type, differing only in handling single versus multiple values. It's not a bad approach, but there might be a better way.

A Cleaner Approach: One Method for Both Cases

Instead of maintaining separate methods, you can merge them into one, using Laravel's whereIn() to handle both single and multiple values:

/**
* Scope a query to filter by a given type or multiple types.
*
* @param string|string[] $types
*/
public function scopeOfType(Builder $query, string|array $types): void
{
$query->whereIn('type', (array) $types);
}

With this approach, you can pass either a single status or an array of statuses, and Laravel will handle it smoothly. By using (array) $types, you ensure that a single string is treated as an array with one element, making the query work for both cases.

However, it's important to note that an empty array passed to whereIn() will actually generate an invalid SQL query. To prevent this, you can add a guard clause to either return the original query or throw an exception:

public function scopeOfType(Builder $query, string|array $types): void
{
if (empty($types)) {
// Option 1: Return query without modification
return $query;
 
// Option 2: Throw an exception
throw new \InvalidArgumentException('Types input cannot be empty');
}
 
$query->whereIn('type', (array) $types);
}

Does it Impact Query Performance?

A common question is whether using whereIn() for a single value affects query performance. While Laravel technically generates different queries for where() and whereIn(), the database engine (whether it's MySQL, PostgreSQL, or another relational database) optimizes both cases to produce the same execution plan.

In other words, the database engine knows how to handle both cases efficiently, so there's no need to worry about performance issues when simplifying your code this way.

Wrapping Up

Instead of creating separate scope methods for single values and arrays, you can simplify things by using one method with type casting. This approach simplifies scope handling, saves time, reduces code repetition, and enhances maintainability.

Next time you're creating scope methods for similar tasks, remember this simple trick!