Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

EuroFighter53's avatar

Query many to many relation in Laravel Controller

Hello everyone, I'm a beginner in the coding world and I'm facing a problem I don't know how to solve. I'm building a simplified replica of AirBnB, I give the user the possibility to select some services to have a list of apartments with those services. I have an Apartments table and a Services table in my db, in between them there's a many to many relation, so there's the pivot table with apartment_id and service_id. The question is: how do I retrieve from the db only the apartments with the selected services? (and obviously also the apartments with more services) There are some of the solutions I have found on the Internet:

$test = Apartment::whereHas('services', function($q) use ($userServices) { $q->whereIn('services.id', $userServices); })->get();

foreach($userServices as $userService) { $query = Apartment::whereHas('services', function($q) use($userService){ $q->where('apartment_service.service_id', $userService); }); } $test2 = $query->get();

Both of them do not work properly, because they retrieve also apartments that don't have the selected services. Does anyone know how to do it?

0 likes
16 replies
Sinnbeck's avatar

This one should give you exactly what you describe, so let us try and debug why it isn't working :)

$test = Apartment::whereHas('services', function($q) use ($userServices) { $q->whereIn('services.id', $userServices); })->get();

Can you show the 2 relationships first? Oh and what is the content of ($userServices);

EuroFighter53's avatar

There's a problem with that solution, I'll explain it with an example: none of my apartments has "beach access" service, and 3 of them have the "WiFi" service. Now if the user selects "WiFi" the function returns the 3 correct apartments, but if the user selects also "beach access" the function should not return anything, because there are no apartments with that service, insted it still returns the 3 apartments with "WiFi". $userService is the array of service ids selected by the user. This is is my first post here, so I don't exactly know what you mean by "show the 2 relationships". Do you want me to upload the Models of Apartment and Service?

Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

@EuroFighter53 ah yeah ok. I get what you mean. The trick is to have a filter on top, to ensure that they have at least those. Having is such a filter. If my memory is correct then this should give you the correct result

$test = Apartment::whereHas('services', function($q) use ($userServices) { 
    $q->whereIn('services.id', $userServices);
 })
->withCount(['services' => function($q) use ($userServices) { 
    $q->whereIn('services.id', $userServices); 
}] )
->having('services_count', '=', count($userServices))->get();
Sinnbeck's avatar

Let me know if you if it works and if you want me to explain it

EuroFighter53's avatar

@Sinnbeck unfortunately I get Internal Server Error. I'm currently trying to figure out what this code does and how to fix it, but I keep getting errors. Any idea why?

EuroFighter53's avatar

@Sinnbeck I already tried putting the arrow before "having", but a ")" is missing too. I tried putting it before "->get()" but it still doesn't work. If it can help, when i run the api, the result is this error: "mb_strpos() expects parameter 1 to be string, object given"

EuroFighter53's avatar

@Sinnbeck Nope, still internal server error. Is still says "mb_strpos() expects parameter 1 to be string, object given". I can't figure out why. Maybe wrong syntax somewhere? No idea.

EuroFighter53's avatar

@Sinnbeck Yes, it is now working. I'm so happy right now, I feel like I can't thank you enough. I've been stuck on this problem for over 1 day, thank you so so much.

EuroFighter53's avatar

@Sinnbeck I understand what it does on a very base level, so if you could explain it that would be great :)

Sinnbeck's avatar

@EuroFighter53 you can think of it as 2 queries.

Lets say we have 10 apartments. 5 has 1 matching service and 3 matches all services. And let's say we selected 4 services

First query runs with a filter (whereHas) that ensures all results have at least one of the provided services. You should be able to just remove this, but it might make the query slower (you can test). So we get 5 apartments out

Next we add a select count (*) on the services table to get services_count. We restrict this to the selected services. So the maximum count we can get is the 4 services we selected.

Now finally we put a filter on top of the result using having(), by comparing each resulting rows services_count with the number 4 (the number of services). It's just like where(), but is run after the initial query, so we can check the data of each row being returned. As only 3 apartments have all 4 services, we only get those

1 like

Please or to participate in this conversation.