Laravel variable to where clause - php

I am trying to extend one app to use my new Laravel app. In this scenario I am getting an unknown number of filters and I would like to forward them all to where() clause.
I have made something like this:
private function filterConverter($filter)
{
$f = [];
foreach ($filter as $singleFilter) {
$operator = $this->filterValues[$singleFilter['operator']];
$value = $operator == 'like' ? '%' . $singleFilter['value'] . '%' : $singleFilter['value'];
$f[] = $singleFilter['field'] . ',' . $operator . ',' . $value;
}
return $f;
}
The thing is that I am getting operators like EQUALS and CONTAINS so I need to convert them to = and LIKE.
With this code I am trying to do this:
return response(MyModel::where($filter)->get());
But it doesn't work. Is there any elegant way to resolve this?
EDIT/SOLUTION
Sorry to #HCK as I couldn't quite accept the answer since it doesn't answer my question, but it pointed me on the right track. The solution was to use key, operator, value keys in the array instead of what I had "keyless".
private function filterConverter($filters)
{
$filter = [];
foreach ($filters as $singleFilter) {
$operator = $this->filterMap[$singleFilter['operator']];
$value = $operator == 'LIKE' ? '%' . $singleFilter['value'] . '%' : $singleFilter['value'];
$filter[] = [
'key' => $singleFilter['field'],
'operator' => $operator,
'value' => $value
];
}
return $filter;
}

Not the nicest way to solve it, but this should work:
private function filterConverter($filters)
{
return collect($filters)->map(function ($filter) { // <---
if($filter['operator'] == 'CONTAINS')
{
$filter['value'] = '%' . $filter['value'] . '%';
$filter['operator'] = 'LIKE';
}
else if ($filter['operator'] == 'EQUALS')
{
$filter['operator'] = '=';
}
return collect($filter)->flatten(); // <---
})->toArray(); // <---
}
Here I'm using the Map() function of the Collection class. there is a lot of useful methods provided by this class.

You may follow this way
DB::table('users')
->where(function($query) use ($filter)
{
// You able to access $filter here
// You may able to to generate this block by loop
$query->where('votes', '>', 100)
->where('title', '<>', 'Admin');
})
->get();
Laravel Doc | Advanced Wheres
Exp-1
$filters = [
['key' => 'votes', 'operator' => '>', 'value' => 100]
];
DB::table('users')
->where(function ($query) use ($filters) {
foreach ($filters as $filter) {
if (#$filter['key'] && #$filter['operator'] && #$filter['value']) {
$query->where($filter['key'], $filter['operator'], $filter['value']);
}
}
})->get();
Exp-2
$filters = [
['key' => 'votes', 'operator' => '>', 'value' => 100]
];
DB::table('users')
->where(function ($query) use ($filters) {
foreach ($filters as $filter) {
if (#$filter['key'] && #$filter['operator'] && #$filter['value']) {
$query->whereRaw("{$filter['key']} {$filter['operator']} '{$filter['value']}'");
}
}
})->get();
You may also use scope function Ref
Exp-3 Laravel Scope
class User extends Model
{
public function scopeFilter($query, $filters)
{
foreach ($filters as $filter) {
if (#$filter['key'] && #$filter['value']) {
$query->where($filter['key'], #$filter['operator']?:"=", $filter['value']);
}
}
return $query;
}
}
// Use
User::filter($filters)->get();

Related

Laravel improve exists() boolean queries with eager loading

I'm trying to get rid of queries that look like this:
SELECT EXISTS(
SELECT *
FROM `ignorings`
WHERE `ignorings`.`user_id` = 80101 AND
`ignorings`.`user_id` IS NOT NULL AND
(`ignorable_id` = 79141)
) AS `exists`
SELECT EXISTS(
SELECT *
FROM `favorites`
WHERE `favorites`.`favorited_id` = 341 AND
`favorites`.`favorited_id` IS NOT NULL AND
`favorites`.`favorited_type` = '' App\Review
'' AND `user_id` = 80101
) AS `exists`
I'm trying to do it by eager loading:
auth()->user()->load(['favorite', 'ignoring']);
Here is my User model:
public function ignoring()
{
return $this->hasMany(Ignoring::class);
}
public function isIgnoring($userId)
{
return $this->ignoring()
->where(['ignorable_id' => $userId])
->exists();
}
Here is my blade file:
#if (! auth()->user() || ! auth()->user()->isIgnoring($review->user->id))
How can I get rid of these queries by eager loading a boolean? I want to load all the auth()->users() ignoring relationships, so to speak.
Here is what I came up with so far:
In the Controller method:
if (auth()->user()) {
$ignorings = auth()->user()->ignoring()->get();
foreach ($ignorings as $ignoring) {
$ignoringArray[] = $ignoring->ignorable_id;
}
$favoriteReviews = auth()->user()->favorite()->where('favorited_type', 'App\Review')->get();
foreach ($favoriteReviews as $favoriteReview) {
$favoriteReviewArray[] = $favoriteReview->favorited_id;
}
$favoriteReviewComments = auth()->user()->favorite()->where('favorited_type', 'App\ReviewComment')->get();
foreach ($favoriteReviewComments as $favoriteReviewComment) {
$favoriteReviewCommentArray[] = $favoriteReviewComment->favorited_id;
}
}
if (empty($ignoringArray)) {
$reviews = Review::with([
'product',
'user',
'favorites',
'reviewComments',
'reviewComments.favorites'
])
->where('created_at', '>=', Carbon::today()->subDays(7))
->paginate(20);
} elseif (! empty($ignoringArray)) {
$reviews = Review::whereNotIn('user_id', $ignoringArray)
->with('product', 'user', 'favorites', 'reviewComments.favorites')
->with([
'reviewComments' => function ($query) use ($ignoringArray) {
$query->whereNotIn('user_id', $ignoringArray);
}
])
->where('created_at', '>=', Carbon::today()->subDays(7))
->paginate(20);
}
return view('new-reviews.index', [
'reviews' => $reviews,
'favoriteReviewArray' => $favoriteReviewArray ?? null,
'favoriteReviewCommentArray' => $favoriteReviewCommentArray ?? null
]);
In the blade file:
#if(in_array($reviewComment->id, $favoriteReviewCommentArray ?? []))
#if(in_array($review->id, $favoriteReviewArray ?? []))
It's not pretty but it works.
You can easily use Model::withExists('relation') and $model->loadExists('releation') functions.
so you can use if like this
if($model->relation_exists){
//do something
}

Laravel Query Builder Join vs With Function, Which One is Better?

I want to ask about Laravel Query using Join or With which is better.
In this case there is a short query that I have tried. But there are some things that make me wonder.
In my case, I'm trying to create a list of users using the API. The problem lies in sorting the data.
The problem is divided into several.
If I use With.
The advantage of using with is that I can call the attributes in the model without rewriting the attributes I want to use. But I was confused when calling data related to other tables for me to sort. example query:
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$sortBy = $request->query('sortBy');
$sortDesc = (is_null($request->query('sortDesc'))) ? $request->query('sortDesc') : ($request->query('sortDesc') == 'true' ? 'desc' : 'asc');
$page = $request->query('page');
$itemsPerPage = $request->query('itemsPerPage');
$search = $request->query('search');
$starDate = $request->query('start');
$endDate = $request->query('end');
$start = ($page - 1) * $itemsPerPage;
$query = MemberRegular::query();
$query->with(['users' => function ($subQuery) {
$subQuery->select('id', 'name', 'email', 'phone');
}]);
$query->select(
'id',
'code'
);
if ($search) {
$query->where(function ($subQuery) use ($search) {
$subQuery->where('code', 'like', '%' . $search . '%');
$subQuery->orWhere(function ($q) use ($search) {
$q->whereHas('users', function ($j) use ($search) {
$j->where('name', 'like', '%' . $search . '%');
$j->orWhere('email', 'like', '%' . $search . '%');
})
});
});
}
if ($sortBy && $sortDesc) {
$query->orderBy($sortBy, $sortDesc)->orderBy('id', 'desc');
} else {
$query->orderBy('created_at', 'desc')->orderBy('id', 'desc');
}
if ($starDate && $endDate) {
$query->whereBetween('created_at', [$starDate, $endDate]);
}
$data['totalItems'] = $query->count();
$data['items'] = $query->skip($start)->take($itemsPerPage)->get();
return HResource::collection($data['items'])->additional(['totalItems' => (int) $data['totalItems']], true);
}
If I use Join.
The advantage of using Join is that I can sort data easily if the data is related to other tables. But I have to re-create a new attribute in a collection. example query:
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$sortBy = $request->query('sortBy');
$sortDesc = (is_null($request->query('sortDesc'))) ? $request->query('sortDesc') : ($request->query('sortDesc') == 'true' ? 'desc' : 'asc');
$page = $request->query('page');
$itemsPerPage = $request->query('itemsPerPage');
$search = $request->query('search');
$starDate = $request->query('start');
$endDate = $request->query('end');
$start = ($page - 1) * $itemsPerPage;
$query = MemberRegular::query();
$query->join('users', 'users.id', '=', 'member_regulars.user_id');
$query->select(
'member_regulars.id',
'member_regulars.code',
'users.name',
'users.email',
'users.phone'
);
if ($search) {
$query->where(function ($subQuery) use ($search) {
$subQuery->where('member_regulars.code', 'like', '%' . $search . '%');
$subQuery->orWhere('users.name', 'ilike', '%' . $search . '%');
$subQuery->orWhere('users.email', 'ilike', '%' . $search . '%');
$subQuery->orWhere('users.phone', 'ilike', '%' . $search . '%');
});
}
if ($sortBy && $sortDesc) {
$query->orderBy($sortBy, $sortDesc)->orderBy('member_regulars.id', 'desc');
} else {
$query->orderBy('member_regulars.created_at', 'desc')->orderBy('member_regulars.id', 'desc');
}
if ($starDate && $endDate) {
$query->whereBetween('member_regulars.created_at', [$starDate, $endDate]);
}
$data['totalItems'] = $query->count();
$data['items'] = $query->skip($start)->take($itemsPerPage)->get();
return HResource::collection($data['items'])->additional(['totalItems' => (int) $data['totalItems']], true);
}
If using Query With The problem lies in sending the sortBy parameter like the following users.name it will be an error because the table is not found in the query I made, but I can immediately call attributes that can be used directly without needing to create a new custom attribute.
If using Query Join, the problem is that I have to re-create custom attributes to be used in data collections, but I don't need to worry about sorting data.
Both are equally important to me. However, if anyone is willing to give advice on the best way I have to use Join or With for this case.
Thank you.
Finally I found the best solution to the problem I was facing. I hope this can help others.
Here I choose to use Join why? because it turns out that I can call the function relations users() in the model that I created so that I can still retrieve custom attributes in the Users model. I don't really know if this is the right way or not. I hope this helps others.
Thank you.

Laravel dynamic whereHas

I am trying to run a query through a relationship path. In human readable format:
Get collection of orders where $order->orderItem->product->sku is LIKE 'red-jumper';
I can create this manually using a whereHas query as follows:
$query->whereHas('orderItems', function($query) use($request){
$query->whereHas('product', function ($query) use($request){
$query->where('sku', 'LIKE', '%' . $request->search . '%');
});
});
However, if I want this to be dynamic and do not know the amount of levels in the relationships, how can I do this?
I would be looking for something lik:
$paths = [
0 => 'orderItems'
1 => 'product,
2 => 'sku'
];
$query->whereHas($paths[0], function($query) use($request, $paths){
$query->whereHas($paths[1], function ($query) use($request, $paths){
$query->whereHas($paths[2], function ($query) use($request, $paths) {
$query->whereHas($paths[3], function ($query) use ($request, $paths) {
$query->where('sku', 'LIKE', '%' . $request->search . '%');
});
});
});
});
Maybe there is a better way to do this all together?
There is a slightly better way in my opinion. You can use dot (.) notation for relationships and collections to turn your $path array into what you want. First we reduce the $path to a dotted relationship and separate the attribute you want to filter by later
$path = ['orderItems', 'product', 'sku']
$param = array_pop($path)
// $path = ['orderItems', 'product']
// $param = 'sku'
$dotPath = collect($path)->reduce(function ($c, $i) {
return $c . $i . '.';
});
// $dotPath = 'orderItems.product.'
$dotPath = substr($dotPath, 0, -1)
// $dotPath = 'orderItems.product'
And then we check with whereHas
$query->whereHas($dotPath, function ($query) use ($request, $param){
$query->where($param, 'LIKE', '%' . $request->search . '%');
})->get();

laravel - using if statement correctly

My code here is working fine but I need it to be short. I'm using if statement so Select if I will use user_id or Guest_ip.
But I get a long code, any help?
if (Auth::guest()) {
$Task = Enrollee::with('path.ProgrammingField')->with(['path.pathtags' => function ($q) use ($TagArray)
{
$q->with(['Tasks' => function ($q) use ($TagArray)
{$q->has('tasktags', '=', 2)->orderBy('id', 'ASC') ->whereDoesntHave('tasktags',
function ($query) use ($TagArray) {
$query->whereNotIn('name', $TagArray);
}
)
->with('tasktags');
}]
)->orderBy('id', 'ASC');
}])
->where( 'user_id' , '=' , Auth::user()->id )
->where('Path_id', $Path->id) ->get();
$Tasks = $Task[0]->path;
$Subs = Enrollee::where( 'user_id' , '=' , Auth::user()->id )->where('Path_id', $Path->id)->get();
$AllSubs = [];
foreach($Subs as $sub){
$AllSubs[] = $sub->task_id;
}
$AllSubTasks = implode(" ",$AllSubs);
$SubTasks = explode(",", ($AllSubTasks));
}
else {
$Task = Enrollee::
with('path.ProgrammingField')
->with(['path.pathtags' => function ($q) use ($TagArray)
{
$q->with(['Tasks' => function ($q) use ($TagArray)
{$q->has('tasktags', '=', 2)->orderBy('id', 'ASC')
->whereDoesntHave('tasktags',
function ($query) use ($TagArray) {
$query->whereNotIn('name', $TagArray);
}
)
->with('tasktags');
}]
)->orderBy('id', 'ASC');
}])
->where( 'guest_ip' , '=' , '127.0.0.1' )
->where('Path_id', $Path->id) ->get();
$Tasks = $Task[0]->path;
$Subs = Enrollee::where( 'guest_ip' , '=' ,'127.0.0.1') ->where('Path_id', $Path->id)->get();
$AllSubs = [];
foreach($Subs as $sub){
$AllSubs[] = $sub->task_id;
}
$AllSubTasks = implode(" ",$AllSubs);
$SubTasks = explode(",", ($AllSubTasks));
}
Can I Use
if (Auth::guest()) {
->where( 'guest_ip' , '=' , '127.0.0.1' )
}
I need to be one code and change if I use guest_ip or user id Using if statement
Hope this will help you.
$ObjTask = Enrollee::with('path.ProgrammingField')
->with(['path.pathtags' => function ($q) use ($TagArray) {
$q->with(['Tasks' => function ($q) use ($TagArray) {
$q->has('tasktags', '=', 2)->orderBy('id', 'ASC')
->whereDoesntHave('tasktags', function ($query) use ($TagArray) {
$query->whereNotIn('name', $TagArray);
}
)
->with('tasktags');
}]
)->orderBy('id', 'ASC');
}])
->where('Path_id', $Path->id);
$Subs = null;
if (Auth::guest()) {
$Task = $ObjTask->where('user_id', '=', Auth::user()->id)->get();
$Subs = Enrollee::where('user_id', '=', Auth::user()->id)->where('Path_id', $Path->id)->get();
}
else {
$Task = $ObjTask->where('guest_ip', '=', '127.0.0.1')->get();
$Subs = Enrollee::where('guest_ip', '=', '127.0.0.1')->where('Path_id', $Path->id)->get();
}
$Tasks = $Task[0]->path ?? null;
$AllSubs = [];
$AllSubTasks = $SubTasks = null;
if (!empty($Subs)) {
foreach ($Subs as $sub) {
$AllSubs[] = $sub->task_id;
}
$AllSubTasks = implode(" ", $AllSubs);
$SubTasks = explode(",", ($AllSubTasks));
}
After properly indenting the code (like #castis correctly advised) , you can start extracting the bits within the statements into methods. That's called refactoring.
From wikipedia:
Code refactoring is the process of restructuring existing computer code—changing the factoring—without changing its external behavior.
It will look something like this:
if(true) {
doWhateverNeedsToBeDone();
} else {
doTheOtherThing();
}
And you copy all code and paste into the body of these new methods.

Laravel query with "if" conditions?

I'm trying to make an advanced search form with Laravel 4, and this is the query:
$result = DB::table('users_ads')
->join('ads', 'users_ads.ad_id', '=', 'ads.id')
->orderBy($column, $method)
->where('status', TRUE)
->where(function($query) use ($input)
{
$query->where('short_description', $input['search'])
->where('category', $input['category'])
->where('product', $input['product']);
})
->join('users', 'users_ads.user_id', '=', 'users.id')
->select('ads.id', 'ads.img1', 'ads.short_description', 'ads.category', 'ads.product', 'ads.price', 'users.city')
->get();
return $result;
The problem is that the user might not use all the input fields. So i want to include some if conditions in this part:
$query->where('short_description', $input['search'])
->where('category', $input['category'])
->where('product', $input['product']);
.. so if the input is empty, to remove the "where" condition.
You could wrap each where in an if statement.
$query = DB::table('user_ads')
->join('ads', 'users_ads.ad_id', '=', 'ads.id')
->orderBy($column, $method);
if ($input['search']) {
$query->where('short_description', $input['search']);
}
if ($input['category']) {
$query->where('category', $input['category']);
}
$query->join('users', 'users_ads.user_id', '=', 'users.id')
->select('ads.id', 'ads.img1', 'ads.short_description', 'ads.category', 'ads.product', 'ads.price', 'users.city')
$result= $query->get();
return $result;
Something along those lines would work I believe.
$filters = [
'short_description' => 'search',
'category' => 'category',
'product' => 'product',
];
.....
->where(function($query) use ($input, $filters)
{
foreach ( $filters as $column => $key )
{
$value = array_get($input, $key);
if ( ! is_null($value)) $query->where($column, $value);
}
});
Newer version of Laravel have a when method that makes this much easier:
->where(function ($query) use ($input, $filters) {
foreach ($filters as $column => $key) {
$query->when(array_get($input, $key), function ($query, $value) use ($column) {
$query->where($column, $value);
});
}
});
$role = $request->input('role');
$users = DB::table('users')
->when($role, function ($query) use ($role) {
return $query->where('role_id', $role);
})
->get();

Categories