Laravel - select with relationship with one query - laravel

I have a Person model. Each person may have zero or more cars:
class Person extends Model
{
public function cars()
{
return $this->hasMany('App\Car');
}
}
I wish to select and display all persons who have a ford with one running query. So i tried this:
$persons = Person::whereHas('cars', function ($query) {
$query->where('mark', 'ford');
})->get();
foreach ($persons as $person) {
foreach($person->cars()->get() as $car) {
print $person->name . " has a " . $car->mark . $car->model
}
}
The $persons is gotten with one query, but inside the foreach loop $person->cars()->get() creates a new query for each person. How can i avoid this and get the needed car data with the first query?

You have to add the mark filter to whereHas() and with():
$persons = Person::whereHas('cars', function ($query) {
$query->where('mark', 'ford');
})->with(['cars' => function($query) {
$query->where('mark', 'ford');
}])->get();

The issue is in cars()
Use the below given snippet
foreach ($persons as $person) {
foreach($person->cars as $car)
{
print $person->name . " has a " . $car->mark . $car->model
}
}
When u do cars() it refers to model which execute another query. But when u use cars it only refers to collection which is already loaded.
Hope this helps

it is worth examining : laravel Eager Loading
When used in this manner will come in a query relational data.
Example:
Person::with('cars')->get();
This code response:
Person data with Person's Car data

Related

laravel multiple relation query

I have 3 model in my project and i want to get a query or collection result as i say:
JewelsItem model:
protected $table = 'jewel_items';
public function jewel(){
return $this->belongsTo('App\Models\Jewel');
}
public function sellInvoice(){
return $this->belongsTo(SellInvoice::class,'sell_invoice_id');
}
2.Jewel model:
public function jewelsItems(){
return $this->hasMany('App\Models\JewelsItem');
}
3.sellInvoice model:
protected $table = "sell_invoices";
public function jewelsItems(){
return $this->hasMany(JewelsItem::class,'buy_invoice_id');
}
Query: i want to get all jewelsitems that has no sellInvoice and has jewel name like 'something'.
note: jewel model has a name attribute .and i want to add name of jewel to first of all the collection's items of result.
i know how to get all jewel with name of 'something' :
Jewel::where('name','like','something'.'%')->get();
but i can't get all jewelItem related to it an add name of jewel to first of them.
To look for condition in another relationship you can use whereHas() and whereDoesntHave()
$name = 'something';
$items = JewelsItem::whereHas('jewel', function($query) use ($name) {
$query->where('name', 'LIKE', "%{$name}%");
})
->whereDoesntHave('sellInvoice')
->with('jewel')
->get();
It reads: getting jewel items where jewel has $name in the name field
and doesn't have sell invoice
then add related jewel to the jewel item.
When you want to retrieve the name of the jewel, you can access its property like so.
foreach($items as $item) {
echo $item->jewel->name;
}

retrieving related field in controller index function gives error but ok in show function

I define the relation in Company table (where I added the plural):
protected $table = 'companies';
public function country() {
return $this->belongsTo(Country::class, "country_id")->withDefault(['country' => 'unknown']);
}
I also did the same in the Country model.
When I use the following code in the controller show function it works:
public function show (Company $company) {
$company->country = $company->country()->pluck('country');
But if I use the same code in the index function in a loop, I get an error "Call to undefined method stdClass::country()":
public function index (Company $company) {
if (request('tag')) {
$companies = Tag::where('name',request('tag'))->firstOrFail()->companies;
$companies->page_title = "Businesses matching tag '".request('tag')."'";
} else {
$companies = DB::table('companies')
->where([['is_active', '=', '1']])
->orderBy('company')
->get();
}
foreach($companies as $key => $thisCompany) {
...
$thisCompany->country = $company->country()->pluck('country');
}
I guess it is due to the fact that $company is created in the loop and not passed through the function like in show(Company $company)... but I could not find how to solve this issue... so help will be appreciated.
I have added the model in the argument of the function and change the name of the $company variable in the loop by $thisCompany to avoid confusion with the $company model.
No error but the field $country->country does not contain the name of the country but "Illuminate\Support\Collection {#443 …1}"
Why is it so complicated? Please help...
Paul, sorry, I think I didn't explain myself well in the comments.
What I meant by "What about if you change DB::table('companies') by Company?", is to stop using DB Query Builder to use the Eloquent Company model.
Specifically in this segment of code:
$companies = DB::table('companies')
->where([['is_active', '=', '1']])
->orderBy('company')
->get();
So, it could be:
$companies = Company::where([['is_active', '=', '1']])
->orderBy('company')
->get();
The explanation is that in the first way (with DB Query Builder), the query will return a collection of generic objects (the PHP stdClass object) that do not know anything about the Company and its relationships.
On the other hand, if you use the Eloquent model Company, it will return a collection of Company objects, which do know about relationships, and specifically the relationship that you have defined as country.
Then, when you loop over the collection, you will be able to access the country relation of each Company object:
foreach($companies as $key => $company) {
//...
$company->country = $company->country()->pluck('country');
}
Finally, your code could looks like:
public function index () {
if (request('tag')) {
$companies = Tag::where('name',request('tag'))->firstOrFail()->companies;
$companies->page_title = "Businesses matching tag '".request('tag')."'";
} else {
$companies = Company::where([['is_active', '=', '1']])
->orderBy('company')
->get();
}
foreach($companies as $key => $company) {
//...
$company->country = $company->country()->pluck('country');
}
//...
}

Order by relationship deep

This is my models structure:
class Family extends Eloquent
{
public function members()
{
return $this->hasMany('App\Person');
}
}
class Person extends Eloquent
{
public function family()
{
return $this->belongsTo('App\Family');
}
public function school()
{
return $this->belongsTo('App\School');
}
}
class School extends Eloquent
{
public function students()
{
return $this->hasMany('App\Person');
}
}
So, a Person belongs to both Family and School.
I fetch a Family:
$family = Family::find(1);
And list the members:
foreach ($family->members AS $member) {
$member->name;
foreach ($member->school AS $school) {
$school->name;
}
}
How can I order the schools by name in ASC order?
EDIT
How to make this list ordered:
foreach ($family->members AS $member) {
$member->school->name;
}
EDIT 2
Very sorry. I've got mixed up.
What I'm trying to achieve is to sort the members of the family by the name of the school that they're attending.
A Person can attend only one school.
Try
$family = Family::with(['members.school'=>function($q){
$q->orderBy('name', 'ASC');
}])->find(1);
The with function will eagerload your records preventing the N + 1 query problem and also make your application run faster.
foreach ($family->members AS $member) {
$member->name;
foreach ($member->school AS $school) {
$school->name;
}
}
hope it helps
I think you need to eager load the relationship then order the relationship:
$family = Family::with(['members.school' => function($query) {
$query->orderBy('name', 'asc');
}])->find(1);
Edit
In response to your comment:
What if every person belongs to only one school, and I want to list
members ordered by the school name, without the second loop? Will
update question with the code in a second
I'm pretty sure you can go ahead and use the same query above with the eager loading as the school() relation on Person is belongsTo so it will return one eloquent record not many. You'd only need another for loop if it was returning an eloquent collection. So you should be able to run the loop like so:
foreach ($family->members AS $member) {
$member->name;
$member->school->name;
}
Unless I'm missing something?
I'll just point out as well that if you always want them sorted by name, you can define a relation on your Person object for school and order it like this:
public function schoolOrderedByName() // you could just call it school()
{
return $this->belongsTo('App\School')->orderBy('name', 'asc');
}
Then you don't have to write the constraint every time you use the relation:
$family = Family::with(members.schoolOrderedByName')->find(1);
Before the second foreach, add this statement
$schools = $member->school->orderBy('name', 'ASC')->get();
Like this
foreach ($family->members AS $member) {
$member->name;
$schools = $member->school->orderBy('name', 'ASC')->get();
foreach ($schools AS $school) {
$school->name;
}
}

Laravel 4 unable to retrieve value from belongsTo relation using query builder

I have two tables, organizations and categories. This is how my tables and models are set up:
Tables:
organizations
id - integer
category_id - integer, fk
name - string
...
categories
id - integer
name - string
Models:
class Organization extends Eloquent
{
public function category()
{
return $this->belongsTo('Category');
}
public function comments()
{
return $this->morphMany('Comment', 'commentable');
}
}
class Category extends Eloquent
{
public $timestamps = false;
public function organization()
{
return $this->hasMany('Organization');
}
}
In my routes.php file, I have the following code (where $category is equal to "organizations"):
$query = Organization::query()
->join('categories', 'categories.id', '=', $category . '.id')
->select('categories.name as category');
$tiles = $query->get();
In my view, I am able to perform the following actions without any errors:
foreach($tile->comments as $comment)
{
...
}
...
$tile->name;
However, calling $tile->category->name will give me the error Trying to get property of non-object. And calling $tile->category will just return null. How can I display the category associated with the organization? I am able to retrieve other properties and relations just fine, but this is giving me a problem.
You have a bug in your code: $category . '.id' should be $category . '.category_id' doing so should make $tile->category->name work.
Also please note (you probably already know this) that in the code that you provided you are not actually using the belongsTo relation as set in your Organization class, you are just using the joined data.
The following would also work using the Eloquent ORM utilizing the models relationship methods:
$tiles = Organization::with('category')
->get();
foreach ($tiles as $tile)
{
echo $tile->name . '<br>';
echo $tile->category->name . '<br>';
}
Or you could do so the other way round from the categories model like so:
$tiles = Category::with('organization')
->get();
foreach ($tiles as $tile)
{
echo $tile->name . '<br>';
foreach($tile->organization as $org)
{
echo $org->name . '<br>';
}
}

Eloquent where condition based on a "belongs to" relationship

Let's say I have the following model:
class Movie extends Eloquent
{
public function director()
{
return $this->belongsTo('Director');
}
}
Now I'd like fetch movies using a where condition that's based on a column from the directors table.
Is there a way to achieve this? Couldn't find any documentation on conditions based on a belongs to relationship.
You may try this (Check Querying Relations on Laravel website):
$movies = Movie::whereHas('director', function($q) {
$q->where('name', 'great');
})->get();
Also if you reverse the query like:
$directorsWithMovies = Director::with('movies')->where('name', 'great')->get();
// Access the movies collection
$movies = $directorsWithMovies->movies;
For this you need to declare a hasmany relationship in your Director model:
public function movies()
{
return $this->hasMany('Movie');
}
If you want to pass a variable into function($q) { //$variable } then
function($q) use ($variable) { //$variable }
whereBelongsTo()
For new versions of Laravel you can use whereBelongsTo().
It will look something like this:
$director = Director::find(1);
$movies = Movie::whereBelongsTo($director);
More in the docs.
is()
For one-to-one relations is() can be used.
$director = Director::find(1);
$movie = Movie::find(1);
$movie->director()->is($director);

Resources