Sort Eloquent models on relationship (one>many) - laravel

I've got a model called "Lesson" which has a ->hasMany() relationship which represent the dates for this lesson.
To retrieve every lesson with a date within a certain time-span I use a whereHas:
$list = Lesson::whereHas('dates', function ($query) use ($start, $end) {
$query->where('date','>=', $start->format('Y-m-d'))
->where('date','<=', $end->format('Y-m-d'));
})->get()
This works as expected, but I now want to sort the $list by the 'smallest' date associated with the lesson.
Preferably I wish to use the Eloquent models, as there are some needed methods in the model. But if it's not possible, than a 'plain' sql statement could also be used.

You can use the available methods of Laravel collections.This code should do it.
$sortedLessons = $lessons->sortBy(function ($lesson) {
$dates = $lesson->dates;
$minDate = $dates->sortBy('date');
return $minDate;
});
I assumed that you have a field date in your date model.

Related

Get data from another model

$objects = Objects::where("id_host", $hostId)
->orderBy("id", "desc")
->get();
In the collection , each object has a type_id field . How can I use this field to get a record in another model and mix them into this object in the response
First thing is first, in order to show the relationship between the records, you'll need to set up a One-to-Many/Many-to-One relationship. This allows you to readily call those relationships from within Laravel and to load them together.
Without being able to see your Type and Object classes, I really can't give specific advice on this, but it should look something like this:
Objects
public function type(): BelongsTo
{
return $this->belongsTo(Type::class);
}
Type
public function objects(): HasMany
{
return $this->hasMany(Objects::class);
}
Once you've done that, you can add a with(...) call to your Eloquent query to eager load the relationship.
$objects = Objects::where("id_host", $hostId)
->orderBy("id", "desc")
->with('type')
->get();
Alternatively, if you don't want to eager load it for some reason, you can call $object->type to get the Type object.

Append Relation Count while using select in eloquent query

So i have a query where i want to select specific columns and relation count on a model.
Something like this
$posts = Post::withCount('comments')->select('title', 'content')->get();
foreach ($posts as $post) {
$post->comments_count // this is not available because of select in query
}
Now when I use select in the query comments_count is no longer available. There is $appends option on the model where I can do something like $appends = ['comments_count'] on the Post model but it will not work. Any idea how to append the data and use the select on model while querying using eloquent.
there is $withCount option on the model as well but it will lazy load while querying comments with post (i.e. inverse relation query).
The problem if that withCount('comments') will generate the comments_count for each post.
Also, the select() overrides the previous withCount(). Simply change the order.
$posts = Post::select('title', 'content')->withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
If you want the global count, you'll need to use a collection method to sum all the counts.
echo $posts->sum('comments_count');

Laravel 5, get only relationship for eloquent model

I have a user and article models in my app. The relation between them is straightforward:
//article.php
use Taggable;
public function user()
{
return $this->belongsTo('App\User');
}
Article uses the Taggable lib which provides me with a variety of methods like Article::withAnyTags (returns all articles tagged with 'xyz' string). Now I'd like to get all users who posted an article/s tagged as 'xyz'. I know how to do this in two lines but something tells me that this is not right. Ideally I'd like to do sth like:
$users = Article::withAnyTags('xyz')->with('user')->select('user'); --pseudocode
Is it possible to do sth like this in eloquent?
Side note: I knot that I could do this with DB::table but this is not an option for me. Please note that there is no "->get()" at the end on my pseudocode. It's so, because I'm paginating the users' results set with lib which works only with eloquent queries.
You could use whereHas():
User::whereHas('articles', function ($query) {
$query->withAnyTags('xyz');
})->paginate();
Or if you need to pass a variable of the tags to the closure you could do:
$tag = 'xyz';
User::whereHas('articles', function ($query) use($tag) {
$query->withAnyTags($tag);
})->paginate();

Laravel collection filtering is slow compared to repository access

I'm refactoring my application towards proper MVC and moving all my business logic to my models. However, in a certain situation this causes a noticeable performance drop.
My model 'User' has a hasmany relationship with the model 'Log'. There's a certain function in my User model that return its logs between a certain date. I filter the collection like this:
public function getLogsBetweenDates($start, $end) {
$logs = $this->logs
->filter(function($value, $key) use ($start, $end) {
return $value->log_date >= $start && $value->log_date <= $end;
});
return $logs
}
This works but its a lot slower than my previous implementation which was accessing my LogRepository in my controller.
public function getLogsBetweenDateFromUser($start_date, $end_date, $user_id)
{
$start = Carbon::parse($start_date);
$end = Carbon::parse($end_date);
return Log::where('user_id', $user_id)
->where('log_date', '>=' , $start)
->where('log_date', '<=' , $end)
->get();
}
It probably has to do with how Eloquent manages retrieving relationships. I would like to keep the code in my User model but not with the performance drop. Does someone know where this performance drop exactly comes from and how I could solve this?
Filtering collections means that you read everything from the database, create objects for each record and then filter. It is normal that it takes much more time. The solution, no matter the architecture, is to do it the second way, using Eloquent filtering.

Eloquent filter on pivot table created_at

I want to filter the contents of two tables which have an Eloquent belongsToMany() to each other based on the created_at column in the pivot table that joins them. Based on this SO question I came up with the following:
$data = ModelA::with(['ModelB' => function ($q) {
$q->wherePivot('test', '=', 1);
}])->get();
Here I'm using a simple test column to check if it's working, this should be 'created_at'.
What happens though is that I get all the instances of ModelA with the ModelB information if it fits the criteria in the wherePivot(). This makes sense because it's exactly what I'm telling it to do.
My question is how do I limit the results returned based on only the single column in the pivot table? Specifically, I want to get all instances of ModelA and ModelB that were linked after a specific date.
OK, here it goes, since the other answer is still wrong.
First off, wherePivot won't work in whereHas closure. It's BelongsToManys method and works only on the relation object (so it works when eager loading).
$data = ModelA::with(['relation' => function ($q) use ($someDate) {
$q->wherePivot('created_at', '>', $someDate);
// or
// $q->where('pivot_table.created_at', '>', $someDate);
// or if the relation defines withPivot('created_at')
// $q->where('pivot_created_at', '>', $someDate);
}])->whereHas('ModelB', function ($q) use ($someDate) {
// wherePivot won't work here, so:
$q->where('pivot_table.created_at', '>', $someDate);
})->get();
You are using Eager Loading Constraints, which constrain only, like you said, the results of the related table.
What you want to use is whereHas:
$data = ModelA::whereHas('ModelB' => function ($q) {
$q->wherePivot('test', '=', 1);
})->get();
Be aware that ModelB here refers to the name of the relationship.

Resources