Ala Chebil
Ala Chebil

Reputation: 493

Laravel HasMany reverse relation issue

lets set a example :

i have a page that show all invoices.

website administrator can filter results with client by searching with they names or last names.

Invoice Model

    public function clients()
{
    return $this->belongsTo('App\Client');
}

Client Model

    public function invoices() {

    return $this->hasMany('App\Invoice');
}

lets say admin want to filter Invoices by Clients and he type for example 'Jhon'

my controller :

1- selecting Client where name LIKE 'Jhon' or where lastname LIKE 'Jhon'

2- if $query return an array with lot of clients have name Jhon .

How i will get Invoices for this clients using the relation ???

Upvotes: 0

Views: 5949

Answers (3)

theWebKeyGuy
theWebKeyGuy

Reputation: 35

Please Keep in mind Toyi is in my opinion the best way to do this I was just trying to make this as simple as possible for you but he is 100% about the Eager Loading as it is way more efficient.

So your using a One to Many Relationship, Where your Clients Model is the parent but now your making the Invoice Model Inverse so the first thing is first you will have to make sure each Invoice has a integer "client_id" that stores each clients id on each invoice. I would also just to be safe and make sure it looks for the right id set your Models like so.

Client Model

public function invoices() {
    return $this->hasMany('App\Invoice', 'client_id');
}

Invoice Model

public function clients() {
    return $this->belongsTo('App\Client', 'client_id');
}

Then in your controller you would pull that data like so.

$client = Client::where('name','like', '%' . Input::get('name') . '%')->orWhere('lastname', 'like', '%' . Input::get('name') . '%')->get();

foreach($client->invoices as $invoice) {
   echo $invoice->title; //Or What ever data you need to pull from your invoice.
}

Please note that I also added the way you would add that input to the orWhere and where statement. Keep in mind the '%' helps check for unexpected % in user input box.

With Eager Loading

Update To This Post I See You Have The Following Code:

$invoices = Facture::with('client')->where(function($q) { 
   $key = Input::get('client'); 
   $q->where('nom', 'LIKE', '%'.$key.'%'); 
   $q->orWhere('prenom', 'LIKE', '%'.$key.'%'); 
})->get();

What I would do is I would pass the input in like this it might fix your error.

$key = Input::get('client');
$invoices = Facture::with('client')->where(function($q) or ($key) {  
   $q->where('nom', 'LIKE', '%'.$key.'%'); 
   $q->orWhere('prenom', 'LIKE', '%'.$key.'%'); 
})->get();

The reason behind this is because Eager loading for what ever reason can't put strings and variable from the out side so I assigned the variable to it by passing it in with the function and made the $key input before the $invoices. If that makes sense. Cheers hope this works for you.

Upvotes: 0

Jorge Rodríguez
Jorge Rodríguez

Reputation: 176

You could do it both ways, depending on what fits best.

Invoice (whereHas):

Invoice::whereHas('client', function ($query){
    $query->where('name', 'like', 'Jhon')
        ->orWhere('lastname', 'like', 'Jhon');
})->get();

Client (flatMap + invoices relationship):

Client::where('name', 'like', 'Jhon')
    ->orWhere('lastname', 'like', 'Jhon')
    ->get()
    ->flatMap->invoices;

Keep in mind the second option suffers from N+1 unless with('invoices') is invoked before executing the query (->get());

Upvotes: 1

Anthony Aslangul
Anthony Aslangul

Reputation: 3847

You should name your relations with the singular or plural form of the thing they represent in order to not get lost later in the development of your project. In your case,

public function clients() {
    return $this->belongsTo('App\Client', 'client_id');
}

should be singular, because an invoice belongs to only one client:

public function client() {
    return $this->belongsTo('App\Client', 'client_id');
}

Now, to actually answer your question, since your are listing invoices, i advice you to start with your Invoice model. Something like that will do the trick:

$invoices = Invoice::with('client')->where(function($q){
    $q->where('firstname', 'LIKE', 'Jhon');
    $q->orWhere('lastname', 'LIKE', 'Jhon');
})->get();

Notice the with('client'), it'll be useful because it'll eager load the client of all your invoices, so you'll not end up with another query for each iteration of your foreach. The documentation about eager loading is available here

An other thing you can do: instead of using 'like', use 'REGEXP'.

$invoices = Invoice::with('client')->where(function($q){
    $q->where('firstname', 'REGEXP', '([[:<:]])Jhon');
    $q->orWhere('lastname', 'REGEXP', '([[:<:]])Jhon');
})->get();

It will give you the invoices of every person where their firstname or lastname start with Jhno. Useless with the word "Jhon", but very useful in many cases: "J" will find every Jack, Jhon, John, Joe...

Last thing, i used with('client') in my examples because i assume you corrected the relation's name. If not, you should use "clients" as it is the name of the relation in the Invoice model :)

Upvotes: 0

Related Questions