How to retrieve multiple relations with multiple tables in laravel eloquent - laravel

I'm using Laravel 5.8 to build a babysitting site. I have 4 tables with different relationships as below:
please see this image
The relationships are:
Babysitter->hasMany(session)
Sessions->hasOne(Review)
Sessions->hasOne(Kids)
Sessions->hasOne(Babysitter)
Sessions->hasOne(Parent)
I want to achieve 2 things:
First one
I want to show this result when listing all babysitters. I'm showing this information for each babysitter:
plsease see this image
See here what I couldn't achieve
plsease see this image
This is my code
Sitters::where('Status', 'active')->where('Verified', 1)->get();
Second one
Also, I've tried to show kids name with parent review as shown here:
plsease see this image
This is what i'm using
Sessions::select('Reviews.*', 'Sessions.Parent_id')->join('Reviews', 'Reviews.Session_id', '=', 'Sessions.id')->with('owner')->where('Trainer_id', session('user')->Id)->where('Status', '=', 'complete')->with('owner')->orderBy('Sessions.id', 'DESC')->get();
Here is Session.php Model
public function owner(){
return $this->belongsTo('App\Models\Parents', 'Parent_id');
}

As discussed change the relations:
Babysitter->hasMany(sesstion)
Sessions->hasOne(Review)
Sessions->belongsTo(Kids)
Sessions->belongsTo(Babysitter)
Sessions->belongsTo(Parent)
First one
in Babysitter.php declare the following attributes
class Babysitter extends Model
{
public function reviews()
{
$this->hasManyThrough(Review::class, Session::class);
}
public function getAverageReviewAttribute()
{
return $this->reviews()->avg('Rating');
}
}
Then you just need to call it on the model instance.
$babysitter = Babysitter::first();
return $babysitter->average_review;
Second one
Just use the relation
$babysitter = BabySitter::with(['sessions' => public function ($session) {
$session->with(['review','parent','kids']);
})->where('trainer_id', '=', session('user')->Id) //did not understand this condition
->first();
This assumes you have parent, kids and review relation declared on Session::class. (change the names if needed)

After a few days of searching & testing, this is what worked for me:
Inside (Sitters) Model, put this relation
public function sessions()
{
return $this->hasMany(Sessions::class, 'sitter_id')
->withCount('reviews')
->withCount(['reviews as review_avg' => function($query){
$query->select(DB::raw('AVG(Rating)'));
}]);
}
Also, inside (Sessions) Model, put this relation
public function reviews()
{
return $this->hasOne(Reviews::class, 'Session_id');
}
Now you query like this
return $sitters = Sitters::with('sessions')->get();
I hope this can help someone :)

Related

How to filter Eloquent collection with data from pivot table

I'm searching for a fine and neat solution to filter my Eloquent collection with the data in the related pivot table. I actually found a solution to my problem already though it feels like my solution is somehow bad. Here is, what I got:
Two models Video and User where every user can track separately the progress on a video. For this I need to save the progress for every video in the ratings table related to the user. When you want to search for just the progress you can do that besides some other filters (category, full text search). What I find a bit quirky is that I have to double "where()" the part where I'm checking the video progress for the logged in user.
Video.php
class Video extends Model
{
use SearchableTrait;
use Taggable;
[...]
public function videocreator(){
return $this->belongsTo('App\User', 'create_user_id');
}
public function users(){
return $this->belongsToMany('App\User', 'progress', 'video_id', 'user_id')
->withPivot('progress_index')
->withTimestamps();
}
[...]
}
User.php
class User extends Authenticatable
{
use Notifiable;
[...]
public function videoscreated(){
return $this->hasMany('App\Video');
}
public function videos(){
return $this->belongsToMany('App\Video', 'progress', 'user_id', 'video_id')
->withPivot('progress_index')
->withTimestamps();;
}
}
VideoController.php
class VideoController extends Controller
{
public function index(Request $request)
{
[...]
$videos = Video::with('videocreator')->with(['users' => function ($query) {
$query->where('users.id', '=', auth()->user()->id);
}])->latest();
if($request->filled('progress') && $request['progress'] !== 'all'){
$videos = $videos->whereHas('users', function($query) use($selectedProgress) {
$query->where('progress_index', $selectedProgress)->where('users.id', '=', auth()->user()->id);
});
}
$videos = $videos->get();
[...]
}
As you can see this part where('users.id', '=', auth()->user()->id) is duplicated. A friend of mine from the Ruby on Rails Faction proposed to come from the User Model first and then fetch the Videos (that's how you would do it there). Though this way you would limit the videos to the user from the progress table. This is not what you want. The App shall track only the per user progress per video, meaning, all the users can see all the videos (just not the progress which is not their own.
Another approach would be to put the part where you filter for the user into the relationship. A method like: myProgress() or something similar.
What is your opinion to that? Is there a more 'eloquent' way to solve that?
Thanks in advance for reading this post!
I would do something like this :
// Get an instance of the videos relationship of the current authenticated user
// Eager load the videocreator relationship
$videoQuery = request()->user()->videos()->with('videocreator');
if ($request->filled('progress') && $request->input('progress') !== "all") {
// Constrain the query : only get the videos with rated_index equal to $selectedProgress
$videoQuery = $videoQuery->wherePivot('rated_index', $selectedProgress);
}
// Finally, run the query against the database
$videos = $videoQuery->latest()->get();

Filter many to many relationship based on child existence and column value

I've been searching for a while and couldn't find an answer, here's what I have:
1- ShowCategory (id & title)
class ShowCategory extends Model
{
public function shows()
{
return $this->belongsToMany(Show::class, 'category_show');
}
}
2- Show (id, title & active)
class Show extends Model
{
public function categories()
{
return $this->belongsToMany(ShowCategory::class, 'category_show');
}
}
So there's a many to many relationship, what I need is retrieving all ShowCategory elements that has at least one Show related to it, and to filter each ShowCategory->shows by show.active, only return shows that are active
Here's what I'm trying to do:
$categories = ShowCategory::whereHas('shows', function($query) {
$query->where('shows.active', '=', true);
})->get();
It only filters ShowCategory that includes shows and if only one of those shows are active, it returns the category with all shows inside, even if others are not active, I need to filter those who are not active.
What should I do? Thanks in advance
This requires a combination of whereHas() and with(). First, whereHas() will filter the ShowCategory model to those that have an active Show, while the with() clause will limit the results of the relationship to only return active ones:
$categories = ShowCategory::whereHas("shows", function($subQuery) {
$subQuery->where("shows.active", "=", true); // See note
})->with(["shows" => function($subQuery){
$subQuery->where("shows.active", "=", true);
}])->get();'
Note: You should be able to use active instead of shows.active, but depends on if that column is on multiple tables.
Using this query, you will get a Collection of ShowCategory models, each with their active Show models already loaded and available via ->shows:
foreach($categories AS $category){
dd($category->shows); // List of `active` Shows
}
This is what you need.
$categories = ShowCategory::whereHas('shows', function($query) {
$query->whereActive(true);
})->get();
Try, this can be a possible way to retreive related results.
// This will only return ShowCategory which will have active shows.
/* 1: */ \ShowCategory::has('shows.active')->get();
// So, logically this will only have active shows -__-
$showCategory->shows
Laravel allows to extends foreign relation by using this . notation as a condition for retreival.
Update
You should update the \ShowCategory model as
public function shows(){
return $this->belongsToMany(Show::class, 'category_show')->where('active', true);
}

Laravel Eloquent Sort By Relationship Column

I have several relationships, one specifically that I would like to use for ordering a list, but I can't seem to find the right way to do it.
Below are my relationships:
public function date(){
return $this->hasOne(agent_billings_dates::class,'id','dateID');
}
public function carrier(){
return $this->hasOne(customer::class,'id','carrierID');
}
As well as two attributes which I have added as appends:
public function getItemCountAttribute(){
return $this->items->count();
}
public function getItemMissingAttribute(){
return $this->itemIssues->count();
}
public function getBatchSumAttribute(){
return $this->items->sum('amount');
These show up all fine when I have the following in my function:
$batches = agent_billings_batches::with(['date','carrier','carrier.carrierDetails'])
->where('status',$request->status)
->get();
But the attributes and the with's fall off when I do this (however the date is sorted appropriately):
$batches = agent_billings_batches::with(['carrier','carrier.carrierDetails'])
->join('agent_billings_dates', 'agent_billings_dates.id', '=', 'agent_billings_batches.dateID')
->orderBy('agent_billings_dates.date','desc')
->where('status',$request->status)
->get();
Am I doing something wrong? I'd appreciate any help anyone could give.
Thanks!
Eloquent does not use Joins when loading relationships. It loads them in a separate query, therefore you cannot order the main result using a relationship at query time, you need to do it after the data is collected:
$batches = agent_billings_batches::with(['date','carrier','carrier.carrierDetails'])
->where('status',$request->status)
->get()
->sortBy(function ($batch) {
return $batch->date->date;
});

How to add condition in connection tables?

I have two tables: Users and Images.
So, a user can have some images.
For this relationship I have additional function in model User:
public function images()
{
return $this->hasMany('App\Images', 'idElement', 'id');
}
And in controller I have:
$users = Users::where('id', $id)->with("images")->get();
How can I add additional condition in controller for images table that will be "where images.type = 1"?
Now this tables are connected only by primary keys, but I need to set a new condition yet.
You can filter your images with callback function, try this:
$users = Users::where('id', $id)->with(["images" => function ($query){
$query->where('type', 1);
}])->get();
For something like this, where you want to scope down a subset of images based on their type, you can add another method called something like public function scopedImages() and define it as such:
public function scopedImages() {
return $this->hasMany('App\Images', 'idElement', 'id')->where("images.type", "=", 1);
}
In your controller, you would access this function the same as you would the images() function on User:
$users = Users::where('id', $id)->with(["scopedImages"])->get();
Keep the function images() as well, so if you need to find all images attached to a User, but adding additional functions like this gives you flexibility on what you want to return and when.

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