Laravel multiple withCount on the same relationship - php

I need to count the results from two different conditions, from the same relationship, but it's being returned as the same name.
Model::where('types_id', $specialism_id)
->withCount(['requests' => function ($query) {
$query->where('type', 1);
}])
->withCount(['requests' => function ($query) {
$query->where('type', 2);
}])
I can access the withCount using $model->requests_count, but because it's querying the same relationship, it appears to overwrite it:
select count(*)
from `requests` where `requests`.`id` = `model`.`id`
and `type` = '1') as `requests_count`,
(select count(*) from `requests` where `requests`.`id` = `model`.`id`
and `type` = '2') as `requests_count`
How can I specify the name for multiple withCount?

You can now do it like this
Model::where('types_id', $specialism_id)
->withCount(['requests as requests_1' => function ($query) {
$query->where('type', 1);
}, 'requests as requests_2' => function ($query) {
$query->where('type', 2);
}])

Option 1
Create two different relationships:
public function relationship1()
{
return $this->hasMany('App\Model')->where('type', 1);
}
public function relationship2()
{
return $this->hasMany('App\Model')->where('type', 2);
}
And use them:
Model::where('types_id', $specialism_id)->withCount(['relationship1', 'relationship2'])
Option 2
Create withCount() like method which will build property with custom names:
public function withCountCustom($relations, $customName)
{
if (is_null($this->query->columns)) {
$this->query->select([$this->query->from.'.*']);
}
$relations = is_array($relations) ? $relations : func_get_args();
foreach ($this->parseWithRelations($relations) as $name => $constraints) {
$segments = explode(' ', $name);
unset($alias);
if (count($segments) == 3 && Str::lower($segments[1]) == 'as') {
list($name, $alias) = [$segments[0], $segments[2]];
}
$relation = $this->getHasRelationQuery($name);
$query = $relation->getRelationCountQuery(
$relation->getRelated()->newQuery(), $this
);
$query->callScope($constraints);
$query->mergeModelDefinedRelationConstraints($relation->getQuery());
$column = $customName; <---- Here you're overriding the property name.
$this->selectSub($query->toBase(), $column);
}
return $this;
}
And use it:
Model::where('types_id', $specialism_id)
->withCountCustom(['requests' => function ($query) {
$query->where('type', 1);
}], 'typeOne')
->withCountCustom(['requests' => function ($query) {
$query->where('type', 2);
}], 'typeTwo')

Related

How can I display other data in foreach?

In my show function in which you can watch every single group, I load all articles with the same tags. And the article with the most votes will be displayed separately at the top. In addition, I indicate the average rating of the items in this group. This is how my function looks like:
public function show($id)
{
$group = group::find($id);
$tagIdArray = $group->tags->pluck('id')->all();
$mostvotedarticle = Article::where('type', 4)->whereIn('privacy', [1, 3])
->whereHas('tags', function($query) use ($tagIdArray) {
$query->whereIn('tags.id', $tagIdArray);
}, '>=', count($tagIdArray))
->orderByVotes()->first();
$articledown = Article::where('status', 1)->whereHas('tags', function($query) use ($tagIdArray) {
$query->whereIn('tags.id', $tagIdArray);
}, '>=', count($tagIdArray))->downVotesAll()->count();
$articleup = Article::where('status', 1)->whereHas('tags', function($query) use ($tagIdArray) {
$query->whereIn('tags.id', $tagIdArray);
}, '>=', count($tagIdArray))->upVotesAll()->count();
$ratio = null;
if($articleup + $articledown == 0) {
$ratio = 0;
} else {
$ratio = ($articleup*100)/($articleup + $articledown);
}
return view('singlegroup',compact('groups','mostvotedarticle', 'ratio'));
}
On a overview page, the individual groups are displayed with a foreach loop:
function:
public function index()
{
$user = Auth::user();
$groups = $user->groups()->latest()->with('tags')->paginate(20);
return view('groups', compact('groups'));
}
view:
#foreach ($groups as $group)
{{$group->name}}
<img src="-----load mostvotedarticle that is in this group------" alt="" />
<div class="progress-bar-primary" style="width:{{$ratio}}%;">
#endforeach
How can I have the mostvotedarticle and group ratings displayed in the foreach loop?
Article Model
public function scopeOrderByVotes($query)
{
$query->leftJoin('Article_activities', 'Article_activities.article_id', '=', 'articles.id')
->selectRaw('articles.*, count(Article_activities.id) as aggregate')
->groupBy('articles.id')
->orderBy('aggregate', 'desc');
}
public function scopeDownVotesAll($query)
{
$query->leftJoin('Article_activities', 'Article_activities.article_id', '=', 'articles.id')
->selectRaw('articles.*, count(Article_activities.id) as aggregate')
->where('Article_activities.activity', '=', '0');
}
public function scopeUpVotesAll($query)
{
$query->leftJoin('Article_activities', 'Article_activities.article_id', '=', 'articles.id')
->selectRaw('articles.*, count(Article_activities.id) as aggregate')
->where('Article_activities.activity', '=', '1');
}
you need put all inside your foreach groups
public function index()
{
$user = Auth::user();
$groups = $user->groups()->latest()->with('tags')->paginate(20);
$ratio = null;
foreach($groups as $key => $group)
{
$tagIdArray = $group->tags->pluck('id')->all();
$mostvotedarticle = Article::where('type', 4)->whereIn('privacy', [1, 3])
->whereHas('tags', function($query) use ($tagIdArray) {
$query->whereIn('tags.id', $tagIdArray);
}, '>=', count($tagIdArray))
->orderByVotes()->first();
$articledown = Article::where('status', 1)->whereHas('tags', function($query) use
($tagIdArray) {
$query->whereIn('tags.id', $tagIdArray);
}, '>=', count($tagIdArray))->downVotesAll()->count();
$articleup = Article::where('status', 1)->whereHas('tags', function($query) use
($tagIdArray) {
$query->whereIn('tags.id', $tagIdArray);
}, '>=', count($tagIdArray))->upVotesAll()->count();
if($articleup + $articledown == 0) {
$ratio = 0;
} else {
$ratio = ($articleup*100)/($articleup + $articledown);
}
$group->mostVote = $mostvotedarticle;
$group->ratio = $ratio;
}
return view('groups', compact('groups'));
}

Laravel many to many that match all data from array

I have products which have a many to many relationship with filters
The user may choose multiple filters and I want to display all products that match the selected filters. But by matching them I mean containing all of them (not only some of them). Here's an example to explain what I mean, let's say that the user is on the cars category page and he wants to filter all cars that are from year 2013 AND have 4x4. Now if the user selects those filters it will show all the cars that are from year 2013 OR have 4x4.
Here's my code in the controller:
public function showFilteredProducts(Request $request)
{
$products = collect([]);
$this->request = $request;
foreach ($request->filters as $filter_id => $active) {
$this->filter_id = $filter_id;
$queriedProducts = Product::whereHas('filters', function($query) {
$query->where('filters.id', $this->filter_id);
})
->whereHas('category', function($query) {
$query->where('slug', $this->request->category_slug);
})
->get();
foreach ($queriedProducts as $product) {
if (!$products->contains($product)) {
$products[] = $product;
}
}
}
return response()->json($products->chunk(3));
}
As i explained this now returns the products if they match only one of the filters, but I want them to match all of them.
try this
public function showFilteredProducts(Request $request)
{
$filters = $request->filters;
$query = Product::query();
foreach ($filters as $filter_id => $active) {
$query = $query->whereHas('filters', function($query) use ($filter_id) {
$query->where('filters.id', $filter_id);
});
}
$query = $query->whereHas('category', function($query) use ($request) {
$query->where('slug', $request->category_slug);
})
$products = $query->get();
return $products->chunk(3);
}
alternatively, based on your previous code, you can use array_intersect like this:
public function showFilteredProducts(Request $request)
{
$products = collect([]);
$this->request = $request;
foreach ($request->filters as $filter_id => $active) {
$this->filter_id = $filter_id;
$queriedProducts = Product::whereHas('filters', function($query) {
$query->where('filters.id', );
})
->whereHas('category', function($query) {
$query->where('slug', $this->request->category_slug);
})
->get();
$products = array_intersect($queriedProducts, $products);
}
return response()->json($products->chunk(3));
}
I think you want to use orWhereHas() instead of whereHas() on the second table that you are checking against.

Laravel Eloquent Relationship with 3 tables

I need to get contact name using laravel Eloquent
I have table stucture below :
CallLogs Table :
id,user_id,PhoneNumber
Phone Table :
id,PhoneNumber,contact_id
contact table :
id,Name
//CallLogs Model :
public function phone()
{
return $this->hasManyThrough('\App\Models\Phone','\App\Models\Contact','id','phoneNumber','phoneNumber');
}
// phone Model :
public function contact()
{
return $this->belongsTo(Contact::class);
}
// Contact Model:
public function phones()
{
return $this->belongsTo(Phone::class, 'contact_id');
}
Join Query :
Please look with->(['phone']) in below query
$data = CallLogs::select('*')->where('call_type', '=', '1')
->when($q, function ($query) use ($q) {
return $query->where(function ($query) use ($q) {
/** #var Builder $query */
$preparedQ = '%' . $q . '%';
$num = 0;
foreach (
[
'to_call',
'from_call',
'callcost',
'created_at'
] AS $field
) {
if ($num) {
$query = $query->orWhere($field, 'LIKE', $preparedQ);
} else {
$query = $query->where($field, 'LIKE', $preparedQ);
}
$num++;
}
return $query;
});
});
//dd($data);exit;
$outgoingcalls = $this->CallLogsFilter->applyFilter($request->get('filter', []), $data);
//$outgoingcalls = $data->paginate($count, ['*'], 'page', $pageNumber);
// Here I am using getting Name
$outgoingcalls = $outgoingcalls->with(['phone'])
->sortable()
->paginate($count, ['*'], 'page', $pageNumber);
$links = $outgoingcalls->appends(Input::except('page', 'table_only'))->links();
$filter = $request->get('search');
return compact('outgoingcalls', 'links','filter');

Laravel array whereIn()

I have a form to filter items:
and I'm looking for something similar to this in Laravel 5.3:
// some variables get from request()->input('...')
$mode = ['A'];
$type = ['a', 'b'];
$group = [0, 1];
// desirable query
$results = Item::whereIn([
['mode_id', $mode],
['type_id', $type],
['group_id', $group]
])->paginate(10);
I can do this
$results = Item::whereIn('mode_id', $mode)
->whereIn('type_id', $type)
->whereIn('group_id', $group)
->paginate(10);
but it's not a dynamic way. For example, if a user select nothing in mode, the query returns an empty array.
We can use conditional clauses:
$results = Item::
when(!empty($mode), function ($query) use ($mode) {
return $query->where('mode_id', $mode);
})
->when(!empty($type), function ($query) use ($type) {
return $query->where('type_id', $type);
})
->when(!empty($group), function ($query) use ($group) {
return $query->where('group_id', $group);
})
->paginate(10);
You can do:
$qb = Item::newQuery();
if (!empty($mode))
{
$qb->whereIn('mode_id', $mode);
}
if (!empty($type))
{
$qb->whereIn('type_id', $type);
}
if (!empty($group))
{
$qb->whereIn('group_id', $group);
}
$results = $qb->paginate(10);
Or build your whereIn associative array w/o empty where's before passing it on.

Laravel belongsToMany with OR condition

How do I create brackets around my orWhere:
public function categories()
{
return $this->belongsToMany('App\Category', 'user_category')->orWhere('default', 1);
}
So it is converted to this:
where (`user_category`.`user_id` = ? or `default` = 1)
Currently the brackets here are missing and mess up the whole query. I tried for example:
public function categories()
{
$join = $this->belongsToMany('App\Category', 'user_category')
->orWhere('default', 1);
return $this->where(function ($query) use ($join) {
return $join;
});
}
But here I am loosing my model and getting Call to undefined method Illuminate\Database\Query\Builder::...
You can use advanced where clause like:
Model::where(function ($query) {
$query->where('a', '=', 1)
->orWhere('b', '=', 1);
})->where(function ($query) {
$query->where('c', '=', 1)
->orWhere('d', '=', 1);
});
Or nested clause like:
Model::where(function($query)
{
$query->where('a', 'like', 'keyword');
$query->or_where('b', 'like', 'keyword');
})
->where('c', '=', '1');
Are you trying to get all the categories related to an user and the categories where the default field is 1?
If that is the case:
public function categories()
{
return $this->belongsToMany('App\Category');
}
public function relatedCategories()
{
return App\Category::all()->where('default',1)->merge($this->categories);
}
The relatedCategories() method should return a collection with the desired categories. Take in care that the non-related categories but with default=1 will not have the pivot object, because these categories doesn't exist in your pivot table.

Categories