getting the latest item of the pivot relationship - laravel

I have a pivot relationship as below:
public function users()
{
return $this->belongsToMany(User::class,'pet_owners','pet_id','owner_id');
}
now in my controller i want to get the latest item of this pivot table which is sorted by created_at field . here is how i get it now :
$pets = Pet::with('users')->get();
return new PetResource($pets);
here I want to show all pets with the latest user entered for that pet in the pivot table

You can use this way,
$pets = Pet::whereHas('users', function($q){
$q->orderBy('id', 'DESC');
})->get();
return new PetResource($pets);
// return latest record of pivot table

Limiting eager loads is unfortunately not easily done with Eloquent. There are many issues related to this on the Github repo. You can read this thread for an overview.
One of Laravel's contributors released a package staudenmeir/eloquent-eager-limit to make it easier to limit eager loads. You can install it with:
composer require staudenmeir/eloquent-eager-limit:"^1.0"
You need to add the \Staudenmeir\EloquentEagerLimit\HasEagerLimit; trait to both your Pet and User models. Then you'll be able to use the limit method on a query inside a with method on your model:
$pets = Pet::with(['users' => function ($query) {
return $query->latest()->limit(1);
}])->get();

Related

Laravel eloquent for four tables

I'm new to Laravel. I am developing a project. and in this project I have 4 tables related to each other
-Users
-Orders
-OrderParcels
-Situations
When listing the parcels of an order, I want to get the information of that order only once, the user information of that order once again, and list the parcels as a table under it. so far everything ok. but I also want to display the status of the parcels listed in the table as names. I couldn't add the 4th table to the query. do you have a suggestion? I'm putting pictures that explain the structure below.
My current working code is
$orderParcels = Orders::whereId($id)
->with('parcels')
->with('users:id,name')
->first();
and my 'orders' model has method
public function parcels(){
return $this->hasMany(OrderParcels::class);
}
public function users(){
return $this->hasOne(User::class,'id','affixer_id');
}
Note[edit]: I already know how to connect like this
$orderParcels = DB::table('order_parcels as op')
->leftjoin('orders as o','op.orders_id','o.id')
->leftjoin('users as u','o.affixer_id','u.id')
->leftjoin('situations as s','op.status','s.id')
->select('op.*','o.*','u.name','s.situations_name')
->where('op.orders_id',$id)->get();
but this is not working for me, for each parcels record it returns me orders and user info. I want once orders info and once user info.
Laravel provides an elegant way to manage relations between models. In your situation, the first step is to create all relations described in your schema :
1. Model Order
class User extends Model {
public function parcels()
{
return $this->hasMany(OrderParcels::class);
}
public function users()
{
return $this->hasOne(User::class,'id','affixer_id');
}
}
2. Model Parcel
class Parcel extends Model {
public function situations()
{
return $this->hasOne(Situation::class, ...);
}
}
Then, you can retrieve all desired informations simply like this :
// Retrieve all users of an order
$users = $order->users; // You get a Collection of User instances
// Retrieve all parcels of an order
$parcels = $order->parcels; // You get a Collection of User instances
// Retrieve the situation for a parcel
$situations = $parcel->situations // You get Situation instance
How it works ?
When you add a relation on your model, you can retrieve the result of this relation by using the property with the same name of the method. Laravel will automatically provide you those properties ! (e.g: parcels() method in your Order Model will generate $order->parcels property.
To finish, in this situation where you have nested relations (as describe in your schema), you should use with() method of your model to eager load all the nested relation of order model like this :
$orders = Orders::with(['users', 'parcels', 'parcels.situations'])->find($id)
I encourage you to read those stubs of Laravel documentation :
Define model relations
Eager loading
Laravel Collection
Good luck !
Use join to make a perfect relations between tables.
$output = Orders::join('users', 'users.id', '=', 'orders.user_id')
->join('order_parcels', 'order_parcels.id', '=', 'orders.parcel_id')
->join('situations', 'situation.id', '=', 'order_parcels.situation_id')
->select([
'orders.id AS order_id',
'users.id AS user_id',
'order.parcels.id AS parcel_id',
'and so on'
])
->where('some row', '=', 'some row or variable')->get();

Laravel: With and whereHas to filter second relation hasOne

i'm trying to filter the table using "with" and "whereHas" for the relation and have it follow a second second relation.
Is it possible to do it with "with" or would it only be possible with "Joins"?
Ticket >> StatusHistory (Last record) >> StatusName = 'new'
ticket
-id
-name
status_history
- ticket_id
- status_name_id
- timestamps
status_names
- id
- name (new, close, paused)
<?
class Ticket extends Model
{
public function latestStatus()
{
return $this->hasOne(StatusHistory::class, 'ticket_id', 'id')->latest();
}
class StatusHistory extends Model
{
public function statusName()
{
return $this->hasOne(StatusName::class, 'id', 'status_name_id');
}
This usually works well if there is only one Status history record, but if there are more, it returns values that should not be there.
example: ticket_id 1 has in history first status new and them status paused
With this sentence he returned the ticket to me even so he no longer has the last status in "new".
Ticket::with('latestStatus')
->whereHas('latestStatus.statusName', function($q){
$q->where('name', 'new');
})
According to the documentation (https://laravel.com/docs/8.x/eloquent-relationships#constraining-eager-loads) it is possible. It would look like this:
Ticket::with(['latestStatus' => function($q){
$q->where('name', 'new');
}])->get();
So that the subquery is linked to the relation you are trying to load
To access the first relationship you just use:
$ticket = Ticket::find($id);
$ticket->latestStatus
By having a "hasOne" relationship established, this will return the related record, which from what I see also has a hasOne relationship, so you can do the following:
$ticket->latestStatus->statusName
In this way, you are accessing the second relationship and working it as usual.
However, this is not the only way, as Laravel also offers access to chained relationships through the "has-one-through" method, which according to the documentation is defined as:
"...this relationship indicates that the declaring model can be matched with one instance of another model by proceeding through a third model."
class Ticket extends Model{
public function statusName()
{
return $this->hasOneThrough(StatusName::class, StatusHistory::class);
}
}
Take into account that for this you must follow the conventions established by Laravel. I leave here the related links, I am sure they will be very helpful. Greetings.
Relationships: one-to-one
Relationships: has-one-through

Is there an Eloquent way of doing a leftjoin in Laravel 5.4

Is there a eloquent way to do a left join in Laravel?
We'd like to get all games and fill in the progress for each one if it exists in user_games.
Right now we've written the following solution, however this isn't eloquent at all, which we like it to be.
public function usergames($user_id) {
return DB::table('games')->leftJoin('user_games', function ($join) use ($user_id) {
$join->on('user_games.game_id', '=', 'games.id')->where('user_games.user_id', '=', $user_id); })->get();
}
DB model:
Thanks in advance!
A way to do this without you actually writing a left/inner join is to use the eloquent relationships.
In your case you will have 2 model classes: User and Game
class User extends Model {
public function games() {
return $this->belongsToMany(App\Game::class);
}
}
Now, you can access the user's games like so:
$user = App\User::find($user_id);
$usergames = $user->games; // Illuminate\Support\Collection
If you want to get a list of users with games, then look into eager loading. That would look something like this:
User::with('games')->get();
This way, Eloquent will know to lazy load the relationship meaning it will only run 2 queries. One to grab the users. and one to grab the games associated with the user, and then make them available for you in the 'games' property of the user object.

Laravel 5.1 - Trending Posts - Query on relationship count with timestamp condition of pivot

I am using laravel 5.1 for a CMS development. I have a simple structure of posts, users and users can like posts.
I want to list the trending posts (posts with most likes in last 1 week in desc order). Posts and Users have many-to many relationship and use a pivot table for relationship.
Posts Model has
public function likedby()
{
return $this->belongsToMany('App\Models\User','user_like_post')
->withTimestamps();
}
User Model has
public function likes(){
return $this->belongsToMany('App\Models\Post','user_like_post')
->withTimestamps();
}
How can I write eloquent query so I receive the trending post. I need to use the timestamp of the pivot table which I find difficult to use.
This is what I tried but it used 'created_at' of post table and not of pivot table.
$trending = Post::with('likedby')->get()
->sortByDesc(function ($post){
$one_week_ago = Carbon::now()->subWeeks(1);
return $post->likedby
->where('created_at','>=',$one_week_ago)
->count();
}
);
You can constrain an eager load within the with method, however this would still load all the posts but only eager load the likedby relationship when it is less than one week old. This is probably not the behaviour you want, but worth mentioning.
Post::with(['likedby' => function($query) {
$query->where('created_at', '>=', Carbon::now()->subWeeks(1));
}])->get();
To only load posts that have been liked within the last week, you would be best served by the whereHas method. This will provide you with a list of posts that have had likes placed on them within the last week.
Post::whereHas('likedby', function ($query) {
$query->where('created_at', '>=', Carbon::now()->subWeeks(1));
})->get();

Laravel 3 Eloquent ORM usage

I have the following code which works but doesn't seem to follow the laravel eloquent way:
Article::left_join('images', 'articles.id', '=', 'images.article_id')
->join('article_category', 'articles.id', '=', 'article_category.article_id')
->where('article_category.category_id', '=', $category_id)
->get();
I have 4 tables; articles and categories which have a many to many relationship with each other, a pivot table article_category table which holds the article id and category id and an image table which has one to one relationship with an article.
I setup my models as:
class Category extends Eloquent {
public static function get_articles($category_id) {
return static::find($category_id)->has_many_and_belongs_to('Article');
}
class Article extends Eloquent {
public function categories() {
return $this->has_many_and_belongs_to('Category');
}
public function image() {
return $this->has_one('Image');
}
However I can't seem to get all three bits of info together. I can do:
Category::get_articles($current_category)->get();
To get all articles in a given category but I can't seem to get the image for the article, there seems to be nothing I can chain onto? Unless I'm doing it incorrectly? Is there a trick I'm missing?
I even tried the stripped down version from the docs:
foreach (Article::with('image')->get() as $article) {
echo $article->image->foo;
}
However I get an error: Trying to get property of non-object, even though var_dump shows $article->image is an object! Weird.
Thanks
If you have not setup a model for the image table, do that. The ORM needs the model there so it knows what 'Image' refers to.
Can you get the category information using the ::with method or is that troublesome too?

Resources