Exclude empty or null column with Laravel Eloquent - laravel

How to exclude empty or null columns when getting collections with Laravel Eloquent ?
I tried this but unsuccessfully:
User::where('last_name', 'foo')->get()->filter()

In addition to #pr1nc3 answer, there is a method ->reject() for this specific purpose. It rejects/excludes the items that match the condition. For your case, use it like this:
User::where('last_name', 'foo')->get()->reject(function ($value) { return empty($value); });
All the values that meet the condition empty($value) i.e. the values that are null/empty will be rejected.

You can do the filter in 2 steps
$users = User::where('last_name', 'foo')->get(); //returns your collection
Then you can use filter for your collection like:
$myFilteredCollection = $users->filter(function ($value) { return !empty($value); });
If you still need it in one line then you can do:
Of course you can merge it into one, get() actually outputs the collections but looks a bit ugly i think. Keep your actions separate.
$users = User::where('last_name', 'foo')->get()->filter(function ($value) { return !empty($value); });

Related

Laravel query on Many to Many relationship

I have an API to keep tracked photos and tags. Each photo can have N tags and one tag can be linked to N photos. That's done using 3 tables:
Photo's table.
Tag's table.
photo_tag relation table.
Now I'm working to get all photos tagged with a set of tags the idea is to make requests to my API with a list of tags and get a list of photos that has at least all the tags.
I've been trying with the whereIn operator.
This is my code (now it's all hardcoded):
$photos = Photo::whereHas('tags', function (Builder $query) {
$query->whereIn('tag', ['a5', 'mate', 'Brillante']);
})->get();
return response()->json($photos, 200);
When I execute it it return all that photos that match one tag and I need only photos that hast all the requested tags (in this example a5, mate).
I'm working on Laravel 9.
Edit:
As Tim Lewis suggested I've tried looping:
$tags = array("a5", "mate", "Brilante");
$photoQuery = Photo::query();
foreach($tags as $tag) {
\Log::debug($tag);
$photoQuery->whereHas('tags', function($query) use ($tag) {
return $query->where('tag', $tag);
});
}
$photos = $photoQuery->get();
Now it's returning an empty list I think because is looking for Photos that only have the 3 tags I hardcoded on the array.
Edit 2:
It seems that those changes were right, but for some reason Postman was not showing me any results of those changes are the solutions to my issue.
Since the whereIn() method matches against any of the values provided, and not all, you'll need to modify this. Specificying a number of whereHas() clauses, 1 for each Tag, should work:
$photoQuery = Photo::query();
foreach ($request->input('tags') as $tag) {
$photoQuery = $photoQuery->whereHas('tags', function ($query) use ($tag) {
return $query->where('tag', $tag);
});
}
$photos = $photoQuery->get();
Now, depending on the tags being sent to your API (assuming through the $request variable as a 'tags' => [] array), this query will include a whereHas() clause for each Tag, and only return Photo records that have all specified Tags.

Query Builder filter for multi level deep relationship in Laravel

I have a selection of plots which each belong to a development by a hasManyThrough relationship through housetypes. I want to filter these by development on their overview page. Plots has a housetype_id column and housetypes has a development_id column.
public function plots()
{
return $this->hasManyThrough(Plot::class, Housetype::class);
}
When I use my filter it returns the developments ID number as $development, I then need this to only show plots which are linked to that development.
I have looked into using whereHas or Join methods but have been unable to figure this out. Current filter scope is below. Thanks
public function scopeFilterDevelopment($query)
{
$development = request()->input('filter_development');
if ($development == "") {
return;
}
if(!empty($development)){
$query->where('development_id', $development);
}
}
If I can understand it right you wish to assert a condition on other Model, HasMany will load all the objects to the related model once the query is completed. Eloquent then binds the related model objects to each.
Try joins from Laravel instead. I feel this is what you exactly want: https://laravel.com/docs/5.8/queries#joins
I would use whereHas to filter the relationship:
YourModel::whereHas('plots', function($query) {
$query->filterDevelopment();
})->get();
I would also edit the query scope not to rely on the request global function, but instead pass the development of value as a parameter.
you have make a leftjon and then use when, you dont have to use
if(!empty($development)){
$query->where('development_id', $development);
}
this any more, you can use
->when($development=="" ? false : true, function($query) use ($development){
return $query->where('development_id', $development);
})
this is a full example
$queryBuilder = DB::table('facturas')->
leftJoin('clientes','clientes.id','=','facturas.clientes_id')->
select('facturas.estados_id as estado','facturas.numero as
numero',DB::raw('concat(clientes.nombre," ",clientes.apellido) as cliente'))->
when($estados===null ? false: true,function($query) use ($estados){
return $query->whereIn('facturas.estados_id', $estados);
})
It was a whereHas that solved this in the end! (another developer at work walked me through this)
Relationship -
public function housetype()
{
return $this->belongsTo(Housetype::class);
}
Function -
public function scopeFilterDevelopment($query)
{
if (request()->input('filter_development') == "") {
return;
}else{
$query->whereHas('housetype', function($housetype){
$housetype->where('development_id', request()->input('filter_development'));
});
}
}
This then returns any plot where its housetype has a matching development_id for the filter_development from the request.
Thanks for everyone's input

Does "where" works with mutattors in eloquent?

I have a mutattor in an eloquent model that generates a "status" atribute.
public function getStatusAttribute(){
if(){
return "enabled";
}
else
{
return "disabled";
}
}
Can I use?
$query = Anuncio::query();
$query->where('status', "enabled" );
return $query->get();
I seems that I cannot. I getting "status" column not defined. How can I get around this problem?
No doesn't works it works on model level
you can use after query from database in collection result
No, when you are doing a query you are asking the database, therefor there needs to be a column status.
There is a way, retrieve x elements from the database and use the Laravel Collection method where(). This is not optimal if you have many elements in teh database, it will retrieve all of them.
Anuncio::all()->where('status', 'enabled')->all();

How to re-order an Eloquent collection?

I've got a collection of records retrieved via a relationship, and I'd like to order them by the created_at field. Is this possible in Eloquent?
Here is how I am retrieving the collection:
$skills = $employee->skills;
I'd like to order this $skills collection by their creation. I've tried $skills->orderBy('created_at', 'desc'); but the Collection class does not have an orderBy method.
I guess this problem is very simple and I'm missing something..
You can do this in two ways. Either you can orderBy your results while query, as in
$employee->skills()->orderBy('created_at', 'desc')->get();
OR
You can use sortBy and sortByDesc on your collection
The reason this is failing is that orderBy is a query method not a collection method.
If you used $skills = $employee->skills()->orderBy('created_at', 'desc')->get();, this would query the skills in the order you want.
Alternatively, if you already had a collection that you wanted to re-order, you could use the sortBy or sortByDesc methods.
You need to add the orderBy constraint on the query instead of the relationship.
For e.g,
$employees = Employee::where('salary', '>', '50000') // just an example
->with('skills') // eager loading the relationship
->orderBy('created_at', 'desc')
->get();
and then:
foreach($employees as $employee)
{
var_dump($employee->skill);
}
If you want the results to always be ordered by a field, you can specify that on the relationship:
Employee.php
public function skills() {
return $this->hasMany(Skills::class)->orderBy('created_at');
}
If you just want to order them sometimes, you can use orderBy(), but on the relationship, not the property:
$skills = $employee->skills()->orderBy('created_at')->get();
Collection has sortBy and sortByDesc
$skills = $skills->sortBy('created_at');
$skills = $skills->sortByDesc('created_at');
This Stackoverflow question askes how to order an Eloquent collection. However, I would like to propose a different solution to use instead given the example in the question. I would like to recommend to use an ordering on the query itself for performance reasons.
Like #Don't Panic proposes you can specify a default ordering on the relationship for great reusability convenience:
app/Models/Employee.php
public function skills() {
return $this->hasMany(Skills::class)->orderBy('created_at');
}
However, if you have already set an ordering on your query like we do in the code above, any additional orderings will be ignored. So that is a bummer if you want to use a different sorting in another situation. To overwrite this default ordering and re-order the query with a new ordering, one needs to use the reorder() method. For example:
// Get a Collections of Skill-models ordered by the oldest skill first.
$skills = $employee->skills()->reorder()->orderByDesc('created_at')->get();
// Same result as the previous example, but different syntax.
$skills = $employee->skills()->reorder()->oldest()->get();
// Or just give some arguments to the reorder() method directly:
$skills = $employee->skills()->reorder('created_at', 'desc')->get();

How to use custom query scopes in an Eloquent subselect query?

This works by manually getting the data as an array then repassing it:
public function scopeWhereWhitelisted($query, $value=true, Tenant $tenant)
{
return $query->where(function($query)use($value,$tenant)
{
$user_id_list = $tenant->getWhiteListedUsersGroup()
->users()
->select('users.id')
->lists('id')
->all()
;
$query->{ $value ? 'whereIn' : 'whereNotIn' }('users.id',$user_id_list);
});
}
But I want this to work (comment // indicates the only difference):
public function scopeWhereWhitelisted($query, $value=true, Tenant $tenant)
{
return $query->where(function($query)use($value,$tenant)
{
$user_id_list = $tenant->getWhiteListedUsersGroup()
->users()
->select('users.id')
//->lists('id')
//->all()
;
$user_id_list = $tenant->getWhiteListedUsersGroup()->users()->select('users.id');//->lists('id')->all();
$query->{ $value ? 'whereIn' : 'whereNotIn' }('users.id',$user_id_list);
});
}
I want to be able to create a "real" subselect without having to have duplicate copies of custom query scopes and relationship queries just for each scope. $tenant->getWhiteListedUsersGroup()->users() is a many-to-many relationship
Here is an example of what has to be done to get a real subselect:
public function scopeWhereWhitelisted($query, $value=true, Tenant $tenant)
{
return $query->where(function($query)use($value,$tenant)
{
$query->{ $value ? 'whereIn' : 'whereNotIn' }('users.id',function($query)
{
$query->from('groups_memberships')
// recreating an existing relationship function
->join('groups','groups.id','group_memberships.group_id')
->select('users.id')
// recreating an already existing query scope
->whereNull('deleted_at')
;
});
});
}
This question will most likely apply to both Laravel 4.0+ and 5.0+
This question is NOT answered by How to do this in Laravel, subquery where in
Restructing code so that the query starts from intended sub query will not work as soon as I need a second non-trivial subselect.
The inclusion/exclusion of ->getQuery() has not made a difference.
I have to choose between a fake subselect or non-DRY custom query scopes.
It seems that the main issue is that the subselect engine is forcing me to use a pre-existing $query object that can't be initialized from an existing relationship.
The recreation of soft deletes (whereNull('deleted_at')) is a trivial example, but I might have to recreate a queryscope that could be relatively complicated already.
Is this whats your going after?
$value; //true or false
$tenant->whereHas('user', function($query) use ($value){
$query->whereHas('groupMembership', function($query) use ($value){
$query->whereHas('group', function($query) use ($value){
if($value){ $query->onlyTrashed(); )
});
})
})
This assumes the group relation includes a withTrashed() call on the relation

Resources