How to get data HasMany() using WhereHas in Laravel - laravel

I want to get data from my table " Package " by using its model " Package "
and in this model " Package " it have a HasMany() named histories() relation to model " History "
so i want to only get data that have histories
here is my controller
public function getIncomeMPW(Request $request)
{
if ($request->expectsJson()) {
$this->getSearch($request);
$query = new Package();
$query->with(['histories', 'items', 'items.prices', 'origin_regency', 'origin_district', 'origin_sub_district', 'destination_regency', 'destination_district', 'destination_sub_district', 'code', 'attachments']);
$query->whereHas('histories', function (Builder $query) {
$query->whereNotNull('partner_id');
});
$query->orderBy('created_at', 'desc');
return (new Response(Response::RC_SUCCESS, $this->query->paginate(request('per_page', 15))))->json();
}
}
here is my Package model relation histories HasMany()
public function histories(): HasMany
{
return $this->hasMany(History::class, 'package_id', 'id');
}
and last here is my response that showing right now
i already try using whereHas(), Has(), whereDoesntHave(), and its seems like there is no impact on my response, can anyone help me please ?

In your response you simply access a different query as it seems.
return (new Response(Response::RC_SUCCESS, $this->query->paginate(request('per_page', 15))))->json();
Uses $this->query
While
$query = new Package();
$query->with(['histories', 'items', 'items.prices', 'origin_regency', 'origin_district', 'origin_sub_district', 'destination_regency', 'destination_district', 'destination_sub_district', 'code', 'attachments']);
$query->whereHas('histories', function (Builder $query) {
$query->whereNotNull('partner_id');
});
$query->orderBy('created_at', 'desc');
Defines a $query without $this. I'd expect your $this->getSearch($request); to define $this->query (as the function is not posted in the question, i cannot tell). So either remove $this in your response - or change everything to $this and ensure to now overwrite it in the first line.
Quickfix should be
return (new Response(Response::RC_SUCCESS, $query->paginate(request('per_page', 15))))->json();
UPDATE:
Quick answer: Change
return (new Response(Response::RC_SUCCESS, $this->query->paginate(request('per_page', 15))))->json();
To
return (new Response(Response::RC_SUCCESS, $query->paginate(request('per_page', 15))))->json();

Wwhat whereHas and whereDoesntHave functions do in the backstage is that they make a sub query such as:
Select * from packages where exists (select * from histories where CONDITIONS YOU HAVE MENTIONED)
And the problem here is that when you use with method you eager load table history which adds one extra query that is not related to the first one such as:
Select * from histories where package_id in (1,2,3,4,5,6)
So since we cleared that out, what I suggest you do is that you assign a function to a variable in this way:
$historyFunction = function ($query) {
$query->whereNotNull('partner_id');
};
and than call it in with and in whereHas methods as shown below:
$query->with(['histories' => $historyFunction, otherRelations... ]);
$query->whereHas('histories', $historyFunction);
And what this does is that it tells eloquent: When you eager load Histories relationship add this conditions to the query you are about to make.

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;

How to use custom model function in eloquent

I want to get all user's online friends, how can I call a custom model function inside the eloquent condition?
this is my code
$friends = $user->friends()->where(function (Builder $query){
$query->where('friend', 'yes');
})
->get();
and this is my function in model
public function getIsOnlineAttribute(): bool
{
// check if the user is online or not
return $this->is_online;
}
I can access is_online after eloquent by foreach, but in my case, I want to check everything in one step ( inside where condition in eloquent). how can I do that???
You can't use conditions for eloquent accessors, in this case you can use (assume 1 is database column value):
$friends = $user->friends()->where('is_online', 1)->get();
or
$friends = $user->friends()->whereIsOnline(1)->get();
or you can create eloquent scope on your model:
public function scopeIsOnline($query) {
$query->where('is_online',1);
}
and you can use this eloquent scope on your controller in this way:
$friends = $user->friends()->isOnline()->get();
this worked for me :)
$friends = $user->friends()
->simplePaginate()
->reject(function ($friend) {
return $friend->is_online === false;
});

How to get latest eloquent relationship by column name in Laravel

I am building a small application on Laravel 5.6 where I am having two models Project and Status. In this I am having a relation as such:
In Project Model I am having:
public function statusUpdate()
{
return $this->hasMany('App\Status','project_id','id');
}
and to retrieve latest status I have:
public function latestStatus()
{
return $this->hasOne('App\Status','project_id','id')->latest();
}
In status I have columns: date, status, sub_status, comments.
I want to retrieve Status where I am having latest status by date mentioned in the column
I tried doing this in my model:
public function latestStatus()
{
return $this->hasOne('App\Status','project_id','id')->latest('date');
}
But this thing is not working out, help me out in this. Thanks
edit
I am using this relation in eager loading something like this:
Project::when( $request->name , function( $q) use( $request ) {
$q->where('name', 'like', '%' . $request->name .'%');
})->with('latestStatus')
->orderBy($request->sort_by_col, $request->order_by)
->paginate(30);
You can use orderBy in the relationship.
public function latestStatus()
{
return $this->hasOne('App\Status','project_id','id')->orderBy('date', 'desc');
}
Try it out.
You got your models wrong. This is what should be in the Project model
public function statuses() //plural because a project has many statuses
{
return $this->hasMany('App\Status','id','project_id');
}
If you want the latest status, call this in your controller:
Project::where('name', 'like', "%{$request->name}%")->with('statuses', function($q) {
return $q->orderBy('date', $request->order_by);
})->paginate(30);
If you want the latest project where the status has changed, first the Status model:
public function project()
{
return $this->hasOne('App\Project','project_id','id');
}
And in your controller:
$project = Status::latest()->first()->project;
Add first to the end of the query.
public function latestStatus()
{
return $this->hasOne('App\Status','project_id','id')->latest()->first();
}
This is how it works
The statusUpdate method builds the query and does the setup for has many relationship
The latest method adds the order by clause
The first methods adds the limit 1 clause, then executes the query and returns the first result

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.

Return first element from relation with `Lazy Eager Loading` [Laravel 5.2]

I have relation like this:
public function message()
{
return $this->hasMany('Engine\Message');
}
inside my Conversation model.
and for each conversation I need to get last message.
Here is what I tried, but this will get only one message for first conversation but will not get message from other conversations...
$con = Conversation::all();
$con->load(['message' => function ($q) use ( &$mess ) {
$mess = $q->first();
}]);
return $con;
I don't wana query for each record... Anyone know how to solve this problem?
As suggested here!
Don't use first() or get() in eager loadings you should create a new relationship in the model.
The model would look something like this...
public function message()
{
return $this->hasOne('Engine\Message');
}
kudos to 'pmall'
Try
$con = Conversation::all();
$con->load(['message' => function ($q) use ( &$mess ) {
$q->orderBy('created_at', 'desc')->take(1);
// or if you don't use timestamps
// $q->orderBy('id', 'desc')->take(1)
}]);
return $con;

Resources