Eloquent eager loading acting as explicit join - php

I am building a small blog and I would like to use the built-in Eloquent eager loading, but I would like it to act as an explicit join.
Here's what I'm trying to do, using a join, which works, but not in the way I want.
$posts = Post::join('users', function($join){
$join->on('users.id', '=', 'posts.user_id');
$join->on('users.status', '=', DB::raw('active'));
})
->get();
My problem with this is that I can't use the user model on my post like so:
$posts[0]->user->firstname;
With the join, the user's data is directly set on the post model, so I have to use it like so:
$posts[0]->firstname;
The thing is: I would like to use my User model because it has a few method inside that I'd like to use. One for printing the full name, one for printing its URL, etc..
What I am not able to do with eager loading, is to prevent a post from loading when it has no user attached to it. When the user associated with the post doesn't have the status 'active', I still get the post, and NULL for the user. But I don't want the post at all.
Is that something possible?
Am I being clear enough in my explications?
Thanks

You're overcomplicating things. Eloquent has everything you need:
// first make sure you load only posts of active users
$posts = Post::whereHas('user', function ($q) {
$q->where('status', 'active');
})
// then eager load the users
->with('user')
->get();
And by the way this is how you do, what you tried, in your join (w/o DB::raw):
$posts = Post::join('users', function($join){
// on() is for comparing fields
$join->on('users.id', '=', 'posts.user_id');
// while where is for comparing to provided values/strings
// just like simple query where()
$join->where('users.status', '=', 'active');
})

Related

Why can't I use latest() in my Eloquent code?

I have been getting an error
'Method Illuminate\Database\Eloquent\Collection::latest does not exist' on this line of my code
$items=Item::select('*')
->orderBy('version','DESC')
->get()
->unique('name')->latest()->paginate(5);
I'm not really sure why and how should I change my code?
latest() is a Builder class method not a Collection class method see: https://laravel.com/api/8.x/Illuminate/Database/Eloquent/Builder.html#method_latest
So use it before getting the collection by get() or paginate():
$items=Item::select('*')
->orderBy('version','DESC')
->latest()
->get()
->unique('name');
Using pagination on unique values is a bit more complicated. We would need to know what to select if there is more records with the same name. The most simple approach is to use distinct() but this may not be what you need:
$items=Item::select('*')
->orderBy('version','DESC')
->latest()
->distinct()
->paginate(5);
Other possibility is to use groupBy('name') instead of distinct(), but there you must carefully aggregate the other columns around the 'name' (or select only the 'name' column). Example:
$items=Item::select('name', DB::raw('MAX(version) as max_version'))
->orderBy('max_version','DESC')
->group_by('name')
->paginate(5);
If you need more columns selected use appropriate aggregate functions MAX() or FIRST(), LAST(), AVG(), etc..
To get the whole row (simple '*' cannot be used):
$max_version_table = Item::select('model_name', DB::raw('MAX(version) as max_version'))->groupBy('model_name');
$items = Item::select('*')->joinSub($max_version_table, 'max_version_table', function($join){
$join->on('items.model_name','=','max_version_table.model_name');
$join->on('items.version','=','max_version_table.max_version');
})->paginate(5);
Laravel's latest method doesn't work on the collection, it works on Query-Builder. So, first, use the latest method and then use paginate.
$items = Item::select('*')
->orderBy('version','DESC')
->latest()
->distinct()
->paginate(5);

laravel elequent way to find single row from table

hello I am new to laravel and maybe I am a bit confused between eloquent and query builder way for writing a query but anyway can you please tell me what could be the best eloquent way to retrieve info like this in laravel 6 or 7
User > hasMany > Recipes
Recipe > belongsTo > User
I want to check if user id 2 present in users table then get only one post which id is 3
Query builder is for explicitly building SQL queries, and does not return instances of your models. Eloquent query builder, is similar but the result will contain the model(s) loaded with all their attributes, and has some handy functions for querying the relations you define in your models.
Given the limited information in your post, I am assuming when you say a post, you mean a recipe:
Query Builder:
DB::table('users')
->join('recipes', 'recipes.user_id', '=', 'users.id')
->select(['users.some_col', ... 'recipes.some_col'])
->where('users.id', 2)
->get();
If you have your models setup with the relations. You can use Eloquent like so:
User::where('id', 2)->with('recipes')->get();
If I understand you correctly it would be like this:
User::whereId($userId) //asuming it is 2
->with(['recipes' => function($q) use($recipeId) {
$q->where('id', $recipeId); //assuming it is 3
}])->first();
you can do this if i understand correctly:
$user = User::findOrFail(2); //auto 404 if user not found
$recipe = $user->Recipes()->where('id',3)->first();
You may use conditional eager loading for better performance.
$userId = 2;
$receiptId = 3;
$user = User::with(['receipts'=> function ($query) use($receiptId){
$query->where('id', $receiptId);
}
])->find($userId)

Laravel: Orderby in related table and with

This is my model
User
Role
and relationship between of those models are many to many.
I want to create query like this:
return User::with('roles')->orderBy('roles.id')->paginate();
I don't want join because I created a base class for every model. I also don't want use orderBy after get because it must load all of my data and after that I should be able to sort and paginate it. So it is not a very good idea.
You can try something like this:
return User::with(['roles' => function ($query) {
$query->orderBy('id', 'desc');
}])->paginate();
But this will only order the eager loading attributes, but if you are interested to use join you can have something like this:
return User::with('roles')
->join('roles', 'user.id', '=', 'roles.user_id')
->orderBy('roles.id', 'desc')
->paginate();
In this you can easily use paginate which is your main concern.
Hope this helps.
User::where('role_id','!=','0')->orderBy('role_id','DESC')->paginate(10);

Eloquent eager loaded query not quite working with joins

I'm working on a Chemical database and am learning Eloquent as I go. This is in Slim Framework, not Laraval itself.
This thread helped get me close to what I need, but now I'm seeing something odd and I haven't been able to find a solution, though lots of people asking similar questions.
I have
$chemicals = $app->Chemical->with(array('Company', 'Room', 'Location', 'Measurement'))
->join('company', 'company_id', '=', 'company.id')
//->join('room', 'room_id', '=', 'room.id')
//->join('location', 'location_id', '=', "location.id")
->where('company', '=', 'ROUSSEL')
->get();
Notice the 2 Joins commented out. Those 2 fields do display properly in my table, but company is blank. If I switch which join is shown, it follows suit.
If I don't use any of the joins, I can do a where just fine with one of the chemical fields. My question is why does having a join seem to break that particular With statement and is there a way to fix it? I get the feeling that it's something to do with the With statement doing some kind of behind the scenes join, but I haven't been able to find any specifics..
Thanks,
Forgot to mention I had tried Eager Loading Constraints before as well.
$chemicals = $app->Chemical->with(
array(
'Company',
'Room',
'Measurement',
'Location' => function ($query) {
$query->where('location', '=', 'FLAMCAB');
}
))
//->where('company', '=', 'FISHER')
->get();
Using above without the where commented out gives me the message that the chemicals table does not have a column called company, which is true.
Not using it, I do get results:
As you can see it does filter the locations table, but it does only that, it doesn't filter chemicals by that, which is what I'm trying to do.
If you want to filter Chemicals based on their related Locations, you can combine constraining eager loads with querying relationship existence:
$filter = function ($query) {
$query->where('location', '=', 'FLAMCAB');
};
$chemicals = $app->Chemical->with(
array(
'Company',
'Room',
'Measurement',
'Location' => $filter
)
->whereHas('location', $filter)
->get();
I'm not sure how efficient this is though - you might want to check out the resulting query. Another option is to simply filter the collection using Collection filters after you run the query.
This question: Complex query with filters with Eloquent might also help you.

Laravel4, eager loading is not working

I'm trying to get "reviews" from my database where the user is from MX (for example) so, I have this:
Review::with(array('user' => function($query)
{
$query->where('country_code', '=', 'MX');
}))
->with('user.country','pictures')
->where('shop_id',Input::get('id'))
->orderBy('id','DESC'))
->get()
->toArray()
But seems to be like where('country_code', '=', 'MX') is not taken into account because retrieve all reviews and I just want the reviews written by the user from MX.
user and picture inside with are functions within my User model
country_code is a field from users table
The goal is: Just get the reviews written by a user from the country specified, and was testing something like this:
Review::where('shop_id',Input::get('id'))
->with('user.country','pictures')
->where('users.country_code','MX')
->orderBy('id','DESC'))
->get()
->toArray()
But is not working as well because says: Unknow column users.country_code in where....
You want to do a query that filters based on the relation. Eager loading constraints only constrain the records that come with your main result set.
Review::whereHas('users', function ($q)
{
$q->where('country_code', 'MX');
})
->with('user','pictures')
->where('shop_id',Input::get('id'))
->orderBy('id','DESC'))
->get();
The whereHas is saying ... give me only Reviews that have a User with country_code=MX.
Reference
Laravel Docs - Eloquent - Querying Relations
If you want reviews written by a certain user or group of users you don't want to use eager loading. Try this:
Review::with(array('user', 'pictures'))
->join('users', 'reviews.user_id', '=', 'users.id)
->where('users.country_code', '=', 'MX')
->where('reviews.shop_id', Input::get('id'))
->orderBy('reviews.id', 'DESC'))
->get();

Categories