Laravel hasMany with multiple constraints - laravel

I have query that I run on my ServiceController
return Service::with('contacts', 'locations.datetimes')->find($service->id);
This works great perfectly, but I need to change it. The relationships are defined as follows:
class Service extends Model
{
public function locations()
{
return $this->belongsToMany('App\Models\Service_location', 'service_location_service');
}
}
class Service_location extends Model
{
public function datetimes()
{
return $this->hasMany('App\Models\Service_detail');
}
}
I need a second constraint on the datetimes where I need the relationship to be along the lines of
return $this->hasMany('App\Models\Service_detail')->where('service_id', $service->id);
The problem is I can't find a way to pass through the $service_id. How do you handle two constraints on a hasMany relationship?

Let try this. Change Service_location to this
class Service_location extends Model
{
public function datetimes()
{
return $this->hasMany('App\Models\Service_detail');
}
public function scopeServiceId($query)
{
return $query->where('service_id', $service->id);
}
}
Now your query will be
Service::with(['contacts', 'locations.datetimes' =>function($q) use($serviceId){
$q->serviceId($serviceId);
}])->find($service->id);
There is no way to pass argument to a relationship function. If you do its highly likely that you will run into the N+1 query problem.

Related

laravel hasManyDeepFromRelations with group by

Models and relations
class Publisher extends Model
{
use HasRelationships;
public function collections(): BelongsToMany
{
return $this->belongsToMany(Collection::class)
->orderBy('title')
->using(CollectionPublisher::class);
}
public function series(): HasManyDeep
{
return $this->hasManyDeepFromRelations($this->collections(), (new Collection())->series());
}
}
class Collection extends Model
{
use HasRelationships, Translatable;
public function editions(): HasMany
{
return $this->hasMany(Edition::class);
}
public function series(): HasManyDeep
{
return $this->hasManyDeepFromRelations($this->editions(), (new Edition)->series());
}
}
class Edition extends Model
{
use HasRelationships, LoadsRelationshipsWithoutScopes, Translatable;
public function series(): BelongsTo
{
return $this->belongsTo(Series::class);
}
public function collection(): BelongsTo
{
return $this->belongsTo(Collection::class);
}
}
And of course there is a Series model.
The problem is that the series relation on the Publisher is returning each Series as many times as there is an Edition for that Publisher in the Series
How do I trim it down so each series is only returned once?
A simple groupBy('series.id') does not work due to the full group by limitations, and I'm not sure how to get around this using the has many deep package.
I finally came up with a solution
On the Publisher model:
public function series(): Builder
{
return $this->hasManyDeepFromRelations($this->collections(), (new Collection())->series())
->getQuery()
->select('series.*')
->groupBy('series.id');
}
The getQuery() part (undocumented in Laravel) limits the scope and allows the groupBy() to work
Thanks to Jonas Staudenmeir for the help

How to Merge Model with Relationship

Please help, I try to merge Model and Relathionship data.
class User extends Authenticatable
{
public function rank()
{
return $this->hasOne('App\rank','id','id');
}
}
in my controller
User::with('rank')->first();
current result : [user:['username':'gugu',rank:['rank':3]]]
what i expect : [user:['username':'gugu',rank:3]]
Alright, relations return collection or an item depending on type of the relation.
What you have to do is to write a accessor
public function getRankAttribute() {
return $this->rank->rank;
}
And then you access rank like this
$user->rank

Laravel 5: Eloquent relationship method with conditions

Normal relationship methods don't usually have a condition, and tend to look like this:
class StripeCustomer extends Model
{
public function user()
{
return $this->belongsTo(User::class, 'stripe_customer_id');
}
}
In my model I have a condition in the relationship method like so:
class StripeCustomer extends Model
{
public function user()
{
if ($this->type === 'normal') {
return $this->hasOne(User::class, 'stripe_customer_id');
} else {
return $this->hasOne(User::class, 'stripe_customer_charity_id');
}
}
}
Does Laravel support conditional relationships in Eloquent like above. A lot of the usual methods still work like so:
StripeCustomer::get()->first()->user;
StripeCustomer::get()->first()->user()->get();
But would the following work predictably:
Foo::with('user')->get();
The issue here is that I am unsure in how the "with" operator works in Eloquent internally.
A reason I believe it also doesn't work is that the user() method needs to be executed for every model. However, when I added a dump(...) at the start of the method, I found it was only run once, indicating that with() does not work.
No, it won't work with with(). What do you think will happen when you try to execute the following code:
Foo::with('user')->get();
The answer is Laravel will create new instance of Foo and try to call user() to get the relationship object. This new instance doesn't have any type ((new Foo)->type will be null), therefore your method user() will always return $this->hasOne(Bar::class, 'b_id') and this relationship object will be used to construct a query.
As you can see this is clearly not what you wanted since only type B users will be eager loaded for all Foo rows. What you need to do in this case is create two relationships (one for each type) and accessors (get/set) for user:
class Foo extends Model
{
public function userA()
{
return $this->hasOne(Bar::class, 'a_id');
}
public function userB()
{
return $this->hasOne(Bar::class, 'b_id');
}
public function getUserAttribute()
{
if ($this->type === 'a') {
return $this->userA;
} else {
return $this->userB;
}
}
public function setUserAttribute($user)
{
if ($this->type === 'a') {
$this->userA()->associate($user);
} else {
$this->userB()->associate($user);
}
}
}
Then you can use with() for both relations to utilize eager loading:
$fooRows = Foo::with('userA', 'userB')->get();
...
foreach ($fooRows as $row) {
$row->user;
}
edit:
Since you've edited code in your question the example code in my answer no longer represents your case, but I hope you get the overall idea.
Yep, with() works. It runs a subquery on any relation your user() method returns. Since your relation already has a constraint, it applies said constraint to the subquery as you'd expect.

Laravel 4 Eloquent relations query

I have a project with main table 'Qsos' and bunch of relations. Now when I try to create advanced search I don't really know how to query all relations at the same time. Qso model has following:
public function band()
{
return $this->belongsTo('Band');
}
public function mode()
{
return $this->belongsTo('Mode');
}
public function prefixes()
{
return $this->belongsToMany('Prefix');
}
public function user()
{
return $this->belongsTo('User');
}
public function customization() {
return $this->hasOne('Customization');
}
Then I have SearchController with following code that has to return collection of all Qsos following required conditions:
$qsos = Qso::withUser($currentUser->id)
->join('prefix_qso','qsos.id','=','prefix_qso.qso_id')
->join('prefixes','prefixes.id','=','prefix_qso.prefix_id')
->where('prefixes.territory','like',$qTerritory)
->withBand($qBand)
->withMode($qMode)
->where('call','like','%'.$input['qCall'].'%')
->orderBy('qsos.id','DESC')
->paginate('20');
And then in view I need to call $qso->prefixes->first() and $qso->prefixes->last() (Qso and Prefix has manyToMany relation) but both return null. What is wrong?
Here is the eloquent code that I found working but taking VERY long time to process:
$qsos = Qso::withUser($currentUser->id)
->with('prefixes')
->withBand($qBand)
->withMode($qMode)
->where('call','like','%'.$input['qCall'].'%')
->whereHas('prefixes', function($q) use ($qTerritory) {
$q->where('territory','like',$qTerritory);
})
->orderBy('qsos.id','DESC')
->paginate('20');

Laravel query multiple tables using eloquent

hi sorry bit of a newbie here but I am have three tables users, profiles, friends. they all have the user_id fields within them and I want fetch all of the fields in one statement using Eloquent and not DB::statement and doing the table joins.
How can I achieve this?
Try this
use the User class and the with method that laravel has to query model relationships
$user = User::with(['profile', 'friend'])->get();
Ensure your models has the correct relationships as follows:
app/models/User.php
public function friend () {
return $this->hasMany('Friend');
}
public function profile () {
return $this->hasOne('Profile');
}
app/models/Profile.php
public function user() {
return $this->belongsTo('User');
}
app/models/Friend.php
public function user() {
return $this->belongsTo('User');
}
use some thing like this:
You should define relations in your models with hasOne, hasMany.
class Review extends Eloquent {
public function relatedGallery()
{
$this->hasOne('Gallery', 'foreign_id', 'local_id');
}
}
class Gallery extends Eloquent {
public function relatedReviews()
{
$this->hasMany('Review', 'foreign_id', 'local_id');
}
}
$gallery = Gallery::with('relatedReviews')->find($id);
Will bring the object Gallery with
$gallery->id
gallery->name
...
$gallery->relatedReviews // array containing the related Review Objects

Resources