Eloquent, Retrieve results from nested relations query - laravel

Here is my query:
$profile = \App\ShippingProfile::findOrFail(2)
::with('costs')
->with( ['methods.zone' => function($query) {
$query->where('id', 2);
}])->get();
So I need shipping profile id 2, with related "costs" model and its related "method" with id 2, and the method's related "zone".
But I don't know how to get the result. If I try ->get() , I get all ShippingProfiles, and if try
->first() I get profile id 1.
So how would I retrieve the results? I have only seen "get", "first" and "find"...or is there something wrong with the query?

There are a few problems with your current code. First of all you have a syntax error:
$profile = \App\ShippingProfile::findOrFail(2)
::with('costs')
The second :: should be ->.
Secondly, when you do firstOrFail() you're actually executing the query and returning a Model as a result. So when you're then chaining the rest onto the Model instance you're likely ending up with an error.
There are a couple of ways you can achieve your goal however. First of all, try using whereKey which adds a where clause to your query against the primary key, (just like using findOrFail does - but it doesn't immediately execute the query and return the model) and use first() to grab the model instance with the relations eager loaded:
$profile = \App\ShippingProfile::whereKey(2)
->with(['costs', 'method.zone' => function ($query) {
$query->where('id', 2);
}])
->first();
Alternatively, if you already have an instance of your model, you can use load(...), in the same way as you would use with(...) to load your relations:
$profile = App\ShippingProfile::findOrFail(2);
$profile->load(['costs', 'method.zone' => function ($query) {
$query->where('id', 2);
}]);

Related

Laravel, eloquent, query: problem with retriving right data from DB

I'm trying to get the right data from the database, I'm retriving a model with a media relation via eloquent, but I want to return a photo that contains the 'main' tag stored in JSON, if this tag is missing, then I would like to return the first photo assigned to this model.
how i assign tags to media
I had 3 ideas:
Use orWhere() method, but i want more likely 'xor' than 'or'
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main')->orWhere();
}]);
return $models->paginate(self::PER_PAGE);
Raw SQL, but i don't really know how to do this i tried something with JSON_EXTRACT and IF/ELSE statement, but it was to hard for me and it was a disaster
Last idea was to make 2 queries and just add media from second query if there is no tag 'main'
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main');
}]);
$models_all_media = Model:: with(['media']);
return $models->paginate(self::PER_PAGE);
but i tried something like
for($i=0; $i<count($models); $i++) {
$models->media = $models_all_media
}
but i can't do this without get() method, beacuse i don't know how to change this to LengthAwarePaginator class after using get()
try using whereHas https://laravel.com/docs/9.x/eloquent-relationships
Model::with('media')
->whereHas('media',fn($media)=>$media->whereJsonContains('custom_properties->tags', 'main'))
->paginate(self::PER_PAGE);
as per your comment you can use
$models = Model::with(['media' => function ($query) {
$query->whereJsonContains('custom_properties->tags', 'main');
}])
->leftJoin('media', function ($join) {
$join->on('models.id', '=', 'media.model_id')
->whereNull('media.custom_properties->tags->main');
})
->groupBy('models.id')
->paginate(self::PER_PAGE);
return $models;

Laravel: Use order by in combination with whereHas

I want to retrieve a collection of data, which is ordered by the start_date of the relation
Basically I want to achieve this, with Laravel Models (the code below works perfectly)
$posts = DB::table('posts')
->leftJoin(
'threads',
'posts.id',
'=',
'threads.postable_id'
)
->where('threads.postable_type', '=', 'App\Post')
->orderBy('threads.start_date')
->paginate($request->input('limit', 2));
So in this case, I'm fetching ALL Posts and those are ordered by the start_date of the thread relation.
Those are not my actual tables but this works perfectly!
Because I'm using https://laravel.com/docs/8.x/eloquent-resources this is not the ideal solution to retrieve sorted data.
So instead I want to use the orderBy clause somewhere here
$posts = Post::whereHas('thread', function ($query) {
$query->where('end_date', '>=', Carbon::now());
});
But I just cannot make this work. I've tried this
$posts = Post::whereHas('thread', function ($query) {
$query->where('end_date', '>=', Carbon::now())
->orderBy('start_date');
});
and I also appended this to the actual relation:
public function thread(): MorphOne
{
return $this->morphOne('App\Thread', 'postable')->orderBy('start_date');
}
If you look at your code:
$posts = Post::whereHas('thread', function ($query) {
$query->where('end_date', '>=', Carbon::now())
->orderBy('start_date');
});
the whereHas will only return Post associate with a thread which the function return true.
Try this:
$posts = Post::with('thread')->has('thread')->orderBy('thread.start_date')->get();
This will fetch all Post with Thread only if they have at least one Thread and then orderBy the start_date of the Thread.
You don't have to do the whereHas function because when you call ->with('thread') it'll use you this :
public function thread(): MorphOne
{
return $this->morphOne('App\Thread', 'postable')->orderBy('start_date');
}
whereHas doesnt retrieve the relationship.
If you need even more power, you may use the whereHas and orWhereHas methods to define additional query constraints on your has queries, such as inspecting the content of a comment: Laravel whereHas
Don't do :
$posts = Post::with('thread')->orderBy('thread.start_date');
If there is no thread on some post, post without thread will be fetch with value null for their key thread and you will have an unexpected result when you try to orderBy.
First of all I want to thank Elie Morin for his help but I found out that I definitely need to use joins for that task.
In my example, I wanted to order the main query (posts) by the relation's start_date
Doing what you suggested
$posts = Post::with('thread')->has('thread')->orderBy('thread.start_date')->get();
Would only order the thread by start_date and not the ENTIRE query.
Which is why I came up with something like this:
$posts = Post::has('thread')
->select('posts.id')
->leftJoin(
'thread',
'posts.id',
'=',
'thread.postable_id'
)
->where('thread.postable_type', '=', 'App\Post')
->where('thread.end_date', '>=', Carbon::now())
->orderBy('thread.start_date')
->with('thread');
return PostResource::collection($posts->paginate(2));

Laravel 7 Query with() and using Where()

Hey guys I have a query that looks like this
$query = Transaction::with(['customer', 'merchant', 'batch'])
->select(sprintf('%s.*', (new Transaction)->table));
I need to filter the transaction based on the iso_id that belons to the current user logged in.
$query = Transaction::with(['customer', 'merchant', 'batch'])
->select(sprintf('%s.*', (new Transaction)->table))
->where('merchant.iso_id', '=', auth()->user()->isIso());
The iso_id I need to compare to, is inside the merchant table
auth()->user()->isIso() returns the correct iso_id if true or sends false if not
So my first try at this was to use where('merchant.iso_id', '=', auth()->user()->isIso())
But that returns that the column does not exist because for some reason, it's not switching from the transaction model to the merchant one.
I am not sure how to use the stuff inside with() as a selector for my where()
Any help would be appreciated!
Try using whereHas to add the constraint:
$query = Transaction::with(['customer', 'batch'])
->whereHas('merchant', function ($q) {
$q->where('iso_id', auth()->user()->isIso());
})
->select(sprintf('%s.*', (new Transaction)->table))
->get();

Where clause inside whereHas being ignored in Eloquent

Im trying to make a query using whereHas with eloquent. The query is like this:
$projects = Project::whereHas('investments', function($q) {
$q->where('status','=','paid');
})
->with('investments')
->get();
Im using Laravel 5.2 using a Postgres driver.
The Project model is:
public function investments()
{
return $this->hasMany('App\Investment');
}
The investments model has:
public function project() {
return $this->belongsTo('App\Project');
}
The projects table has fields id,fields...
The investments table has the fields id,project_id,status,created_at
My issue is that the query runs and returns a collection of the projects which have at least one investment, however the where clause inside the whereHas is ignored, because the resulting collection includes investments with status values different than paid.
Does anyone has any idea of what is going on?
I believe this is what you need
$projects = Project::whereHas('investments', function($q) {
$q->where('status','=','paid');
})->with(['investments' => function($q) {
$q->where('status','=','paid');
}])->get();
whereHas wil check all projects that have paid investments, with will eagerload all those investments.
You're confusing whereHas and with.
The with method will let you load the relationship only if the query returns true.
The whereHas method will let you get only the models which have the relationship which returns true to the query.
So you need to only use with and not mix with with whereHas:
$projects = Project::with(['investments' =>
function($query){ $query->where('status','=','paid'); }])
->get();
Try like this:
$projects = Project::with('investments')->whereHas('investments', function($q) {
$q->where('status','like','paid'); //strings are compared with wildcards.
})
->get();
Change the order. Use with() before the whereHas(). I had a similar problem few weeks ago. Btw, is the only real difference between the problem and the functional example that you made.

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