I think what you are describing here is the Repository pattern. Or at least that is what many of us would call the "middle layer" that you are asking for, which would be responsible for fetching the data. This pattern has been covered here at Laracasts all over the place, but you can also look it up in general on the internet. It is an implementation of the Strategy Pattern, which is covered in this video: https://laracasts.com/series/design-patterns-in-php/episodes/4 And the reason for its existence is different than the problem you're trying to solve. It has been born to make it simpler to switch DB engines, which is not really necessary with something like Eloquent already at our fingertips.
So I don't know if I would ever reach for it in a Laravel app now. Laravel provides other options, like Query Scopes: https://laravel.com/docs/11.x/eloquent#query-scopes These can be organized neatly into classes and traits and then you have them available in any controller.
Another option would be to just use an Action for each query you would want to reuse. Just create a class named however you like, for example: GetUserDetailsand create a method on it, again called whatever you want, for example: execute()and then you can use it in a controller like this:
public function index(GetUserDetails $query)
{
$userDetails = $query->execute();
// ...
}
And you can reuse it anywhere you have dependency injection, furthermore, you have dependency injection available in the constructor of GetUserDetails as well, should you need it. Which means you can test the action itself with unit tests. And also, if you want or need, you can use other actions/queries in it. So no need to create a single "repository" class in order to organize the code into smaller pieces.
And another option is to use an external package like this one from Spatie which has its own rules: https://spatie.be/docs/laravel-query-builder/v5/introduction It constrains you more but can save you some boilerplate, if and only if your usecase needs that. It really depends on what your queries are for. If it's for filtering, it might help you; if not, it might not help you.
And obviously you can always combine all of these options. You can use the package for its use cases, you can have Action/Query classes and for the most reused queries, you can have local or global query scopes as well.