Argument #1 ($haystack) must be of type string, Closure given on Laravel Eloquent
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Deciphering Eloquent Grouping: Solving the stripos() Argument Error in Laravel
As senior developers, we often dive deep into complex Eloquent queries, especially when trying to aggregate nested relationships and group results by dynamic fields. The scenario you're facing—trying to combine whereHas, get(), and groupBy()—is a classic point where the interaction between database results, PHP collections, and closure functions can lead to confusing errors like stripos(): Argument #1 ($haystack) must be of type string, Closure given.
This error isn't usually about the SQL itself; it’s about how Laravel processes the data returned by the database and tries to apply collection methods to it. Let’s break down why this happens and provide a robust solution for achieving your desired grouped results.
Understanding the Error: Why Closures Cause Trouble
The error stripos(): Argument #1 ($haystack) must be of type string, Closure given indicates that a function expecting a string (like stripos, which searches within a string) received a PHP closure object instead. In the context of your Eloquent query, this usually happens when you attempt to apply methods designed for raw strings or simple values (like grouping) directly onto an intermediate collection object or a relationship result before that data has been properly flattened into an array format that the function expects.
When you use $query->get(), you retrieve a collection of Eloquent models. When you immediately chain groupBy(function ($date) { ... }) on this collection, Laravel attempts to iterate over the results. The issue arises because the structure returned by the underlying relationship query isn't always a simple string or a direct date object that format() can handle universally across all database drivers when nested within complex constraints.
The subsequent error, Property [created_at] does not exist on this collection instance, confirms that after the grouping operation failed to produce the expected structure, you couldn't access the grouped data in the way you expected.
The Correct Approach: Aggregation vs. Post-Processing
There are two main ways to approach complex grouping in Laravel Eloquent: using database aggregation (the most efficient method) or post-processing the results in PHP (which is often necessary for highly custom groupings).
Method 1: Leveraging Database Grouping (The Efficient Way)
If your goal is to group by a date derived from a related table (rawrates), the most performant solution is to let the database handle the heavy lifting using groupBy() directly in the query builder, rather than fetching everything and grouping it in PHP.
Since you are starting with RateDescription, you need to ensure that the relationship constraint forces the desired grouping context. In many cases, this requires restructuring the query to use joins and direct aggregation functions if possible, or ensure your Eloquent model definitions facilitate this structure.
Method 2: Correcting Post-Fetch Grouping (The Practical Fix)
Since you are dealing with a complex scenario involving nested data, sticking to fetching the data first and then correctly grouping the resulting collection is often clearer, provided we handle the access path correctly. The key fix involves ensuring that the closure passed to groupBy returns a simple, extractable value from the model instance, not attempting to call methods on the entire collection object.
Let's refine your second attempt to correctly map the data:
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
// Assuming RateDescription has a relationship named 'rawrates'
$ratesbydate = RateDescription::where('company_id', '=', 7)
->whereHas('rawrates', function (Builder $query) use ($sub7days) {
$query->where('created_at', '>=', $sub7days)
->orderByDesc('created_at');
})
// Step 1: Fetch the raw results first
->get()
// Step 2: Apply grouping based on a calculated key from the related data
->groupBy(function ($description) {
// Accessing the related date safely within the context of the model instance
// We assume we are grouping by the rate's creation date.
return Carbon::parse($description->rawrates->created_at)->format('d');
});
// Now, $ratesbydate is a Collection where keys are today's dates (e.g., '1', '2', '3')
// and values are collections of RateDescription models for that date.
Notice the critical change: instead of trying to operate on $date->created_at directly outside the context, we access the related data through the model instance ($description->rawrates->created_at). This ensures that when the groupBy closure executes, it receives a valid value (a date string) rather than attempting to call methods on an entire collection object, resolving the error.
Conclusion: Mastering Eloquent Data Flow
Dealing with complex data retrieval in Laravel Eloquent requires careful consideration of the flow between the database, the ORM, and PHP collections. The stripos() error highlights a common pitfall: mixing collection operations (get(), groupBy()) with accessing nested model properties inside closures can break the expected type contract.
By understanding that groupBy operates on the values returned by your closure, and ensuring that those values are simple strings or comparable types derived directly from the underlying models, you can successfully aggregate complex relational data. Always prioritize checking the structure of the object you are grouping—whether it’s a model instance or a collection—before attempting to call methods on it, especially when dealing with nested relationships, as we advocate when building powerful applications on the Laravel framework. For more advanced insights into optimizing database queries and Eloquent usage, always refer back to the official documentation at https://laravelcompany.com.