laravel eloquent pass variables to with() - php

is it possible to use variables in a with()?
for example:
groep::find(1)->with(['belongsToManyAgent'])
belongsToManyAgent looks like:
public function belongsToManyAgent($ignoreVariables=true, $columns=['*'], $showBoth=false, $showRemoved=false) {
return $this->belongsToMany(
'App\Models\Agent', // doel model
'agent_groep', // pivot tabel
'groep_id', // local key
'agent_id' // foreign key
)
->when(!filter_var($ignoreVariables, FILTER_VALIDATE_BOOLEAN), function($query) use ($showRemoved) {
$query->select($columns)
->when(!filter_var($showBoth, FILTER_VALIDATE_BOOLEAN), function($query) use ($showRemoved) {
$query->where('verwijderd',$showRemoved);
})
->when(filter_var($showBoth, FILTER_VALIDATE_BOOLEAN), function($query) use ($showRemoved) {
$query->when(!filter_var($showRemoved, FILTER_VALIDATE_BOOLEAN), function($query){
$query->where('verwijderd','0');
});
});
});
}
can I access the variables in the function via with() so that I can put $showRemoved on true for example?

There are some options to further narrow down a relationship you have, like so:
$users = User::with(['posts' => function ($query) {
$query->where('title', 'like', '%code%');
}])->get();
See https://laravel.com/docs/9.x/eloquent-relationships#constraining-eager-loads . I don't think it is possible to actually pass an array of variables to your relation function since it is called internally without parameters.
Also you probably want to simplify that function name to just public function agents(...).

Related

Paginate Data from scope

I'm using a scope to get data from a BelongstoMany relation, is it possible to paginate data from this scope ?
User Model
public function scopeWithAndWhereHas($query, $relation, $constraint){
return $query->whereHas($relation, $constraint)
->with([$relation => $constraint]);
}
I'm accessing the scope in a controller like this
$transactions = User::withAndWhereHas('drinks', function($query) use ($status){
$query->where('status', '1');
})->paginate(2);
The paginate() helper doesn't seem to work.

dynamic query laravel eloquent with whereHas

I want to create dynamic filters.
for example I want to create this code
$Contact = Contact::whereHas('User', function ($query) use ($searchString) {
$query->where('name', 'like', '%Jhone%')->orwhere('family', '<>' . 'Doe');
})->whereHas('Account', function ($query) use ($searchString) {
$query->where('account_name', '=' , 'test_account' )->orwhere('account_city', 'like', '%test_city%');
})->get();
and all of parameters is variable
name,like,%Jhone%,family,<>,Doe,.....
and I want to pass variables to function and function create above query.
I assume that the relationship functions within your Contact, User and Account models are written in camelCase and not PascalCase like your example shows.
public function getContacts(Request $request)
{
return Contact::when($request->get('username'), function ($query, $val) use ($request) {
$query->whereHas('user', function ($q) use ($val, $request) {
$q->where('name', 'like', '%'.$val.'%');
if ($request->has('familyname')) {
$q->orWhere('family', '<>', $request->get('familyname'));
}
});
})
->when($request->get('accountname'), function ($query, $val) use ($request) {
$query->whereHas('account', function ($q) use ($val, $request) {
$q->where('account_name', $val);
if ($request->has('city')) {
$q->orWhere('account_city', 'like', '%'.$request->get('city').'%');
}
});
})
->get();
}
This function will return all contacts when no GET parameters are given on the request. If a parameter for username is present, it will only return contacts where a user with the given name exists for. If furthermore a familyname parameter is present, it will give contacts with a user that has a matching username or a familyname different from the one given. The very same applies to the account, accountname and city.
In particular, there are two things interesting about this example:
The when($value, $callback) function can be used to build very dynamic queries which only execute the $callback when $value is true. If you use $request->get('something') and something is not available as parameter, the function will return null and the callback is not executed. The callback itself has the form function ($query, $value) { ... }, where $value is the variable you passed to when() as first parameter.
Using $request->has('something') inside the query builder functions to dynamically build constraints on the query is an alternative to when(). I only added it for the purpose of demonstration - in general I'd recomment sticking to one style.
If you would extend on the example, you could also build highly dynamic queries where not only the variable content like Doe for the family name is given as parameters, but also the comparator like =, <> or like. But extending further on this topic is too much for this answer and there are already tutorials about this topic available anyway.
Edit: here an example for a dynamic query with more detailed input
Expected input (slightly different than your request because yours cannot work):
$filters = [
'user' => [
['name','like','%Jhone%'],
['family','<>','Doe'],
],
'account' => [
['account_name','=','test_account'],
['account_city','like','%test_city%'],
]
];
And the function:
public function getContacts(Request $request, array $filters)
{
$query = Contact::query();
foreach ($filters as $key => $constraints) {
$query->whereHas($key, function ($q) use ($constraints) {
if (count($constraints) > 0) {
$q->where($constraints[0][0], $constraints[0][1], $constraints[0][2]);
}
for ($i = 1; $i < count($constraints); $i++) {
$q->orWhere($constraints[$i][0], $constraints[$i][1], $constraints[$i][2]);
}
});
}
return $query->get();
}
This will always use OR for multiple constraints and not AND. Using AND and OR mixed would require a lot more sophisticated system.

Eloquent scope method gives different results

I'm using Laravel 5.4
Working code
$cityWithEvents = City::with(['events' => function ($q) {
$q->whereDate('start_time', Carbon::today('America/Montreal'))->orwhereBetween('start_time', [Carbon::today('America/Montreal'), Carbon::tomorrow('America/Montreal')->addHours(4)]);
}])->where('active', 1)->get()->keyBy('id');
Not working code
$cityWithEvents = City::with('todayEventsWithAfterHoursIncluded')
->where('active', 1)
->get()
->keyBy('id');
City model
public function events() {
return $this->hasManyThrough('App\Event', 'App\Venue');
}
public function todayEventsWithAfterHoursIncluded () {
return $this->events()
->whereDate('start_time', Carbon::today('America/Montreal'))
->orwhereBetween('start_time', [
Carbon::today('America/Montreal'),
Carbon::tomorrow('America/Montreal')->addHours(4)
]);
}
Questions
When trying to create a scope method the query gives me different result. I can't see why and what should I change
I've only used scopes a few times, but never within a ->with() clause. On your City model, create a new scope:
public function scopeTodayEventsWithAfterHoursIncluded($query){
return $query->with(["events" => function($subQuery){
$subQuery->whereDate('start_time', Carbon::today('America/Montreal'))->orWhereBetween('start_time', [Carbon::today('America/Montreal'), Carbon::tomorrow('America/Montreal')->addHours(4)]);
});
}
Then, on your City query, add it as a scope function:
$cityWithEvents = City->where('active', 1)
->todayEventsWithAfterHoursIncluded()
->get();
I think the way you are using it requires that your Event model has the scope on it, as you're technically calling with("events") on your base query and your scoped one.
Let me know if this changes you results.
If you do the query, you should do it like this:
$cityWithEvents = City::withTodayEventsWithAfterHoursIncluded()
->where('active', 1)
->get()
->keyBy('id');
You scope in you model should look like this:
public function scopeWithTodayEventsWithAfterHoursIncluded ($query)
{
return $query
->with(['events' => function ($q) {$q
->whereDate('start_time', Carbon::today('America/Montreal'))
->orwhereBetween('start_time', [
Carbon::today('America/Montreal'),
Carbon::tomorrow('America/Montreal')->addHours(4)
]);
}]);
}
Now it should be equal.

Laravel 5.1 constrain and eager load multiple nested relations

I have four models: OwnerCompany, Owner, User, and Role.
I want to get all OwnerCompanys eager loading their Owner and also eager loading its Users who have the Role with the name 'admin'.
OwnerCompany::with('owner.users')->whereHas('owner.users.roles', function ($query) {
$query->where('name', 'admin');
})->get();
This loads in the models but doesn't constrain the users, they are all loaded.
Not tested . I think you are missing like or equal operator.
OwnerCompany::with('owner.users')->whereHas('owner.users.roles', function ($query) {
$query->where('name','like','admin');
})->get();
I try this, if work then let me know. not tested
1).
OwnerCompany::with('owner.users.roles')->whereHas('owner', function ($query) {
$query->whereHas('users', function($qr){
$qr->whereHas('roles', function($q){
$q->where('name', 'admin');
});
});
})->get();
OR
2).
OwnerCompany::with('owner.users.roles')->whereHas('owner.users.roles', function ($query) {
$query->where('name', 'admin');
})->get();
If I got it right, you want each OwnerCompany object to contain the Owner object, which will contain "admins". I would do something like this.
$ownerCompany = OwnerCompany::all();
return $ownerCompany->each(function ($company) {
$company->owner;
$company->owner->users->each(function ($user) {
return $user->hasRole('admin');
});
});
and inside User class:
public function role()
{
return $this->belongsTo(Role::class);
}
public function hasRole($role)
{
if ($this->role()->where('name', $role)->first()) {
return $this;
}
return null;
}
Unfortunately, I can't test it right now, so If you have any trouble let me know.

Eloquent Query Scope on Relationships

I have two models, App\Song (belongsTo App\Host) and App\Host (hasMany App\Song).
I have the following query in my Controller:
$songs = Song::whereHas('host', function($query) {
$query->where('skip_threshold', '>', \DB::raw('songs.attempts'))
->where('active', 1);
})
->whereNull('downloaded')
->get();
For reusability I would like to turn into a query scope(s).
I'm quite new to Eloquent so I'm not sure this is the correct way to do this being that its two Models as its not returning any results (where there should be).
Song.php
public function scopeEligable($query)
{
$query->where('skip_threshold', '>', \DB::raw('songs.attempts'));
}
public function scopeActiveHost($query)
{
$query->where('active', 1);
}
public function scopeInDownloadQueue($query)
{
$query->whereNull('downloaded');
}
You should put scopes into Models they belong to. Looking at your initial query scopes scopeEligable and scopeActiveHost belongs to Host model, so you should move them into Host model and then you'll be able to use your query using scopes like this:
$songs = Song::whereHas('host', function($query) {
$query->eligable()->activeHost();
})->inDownloadedQueue()->get();
and as already pointed in comment you should add return to each scope so they could be used as they intended.
EDIT
If you would like to make using it shorter, you could create new relationship in Song model:
public function activeHost()
{
return $this->belongsTo(Host:class)->eligable()->activeHost();
}
so now, you could write:
$songs = Song::whereHas('activeHost')->inDownloadedQueue()->get();
I think you're mistaken about 2 models. I think this should work
Song.php
public function scopeEligable($query, $active) {
return $query->whereHas('host', function($q) {
$q->where('skip_threshold', '>', \DB::raw('songs.attempts'))->where('active', $active);
})
}
public function scopeInDownloadQueue($query)
{
$query->whereNull('downloaded');
}
Usage
$songs = Song::eligable(true)->inDownloadQueue()->get();

Categories