Laravel advanced search query fix

人盡茶涼 提交于 2019-12-31 07:15:33

问题


I have a search form with multiple input and select boxes I need help to get if conditions in my query in order to each part works separately and all at once.

here is my blade codes:

<form action="{{route('advancesearch')}}" method="post">
      {{csrf_field()}}
      <div class="sidebar-title">
        <span>Advanced Search</span>
        <i class="fa fa-caret-down show_sidebar_content" aria-hidden="true"></i>
      </div>
      <!-- ./sidebar-title -->

      <div id="tags-filter-content" class="sidebar-content">
        <div class="filter-tag-group">

          @foreach($options as $option)
          <div class="tag-group">
            <p class="title">
              <span class="filter-title show_filter_content">{{$option->title}} <span class="pull-right"><i class="fa fa-minus"></i></span></span>
            </p>
            <div class="filter-content">
              <div class="checkbox">
              @foreach($option->suboptions as $suboption)
              <label for="suboptions">
                <input name="suboptions[]" type="checkbox" value="{{$suboption->id}}">
                {{ucfirst($suboption->title)}}
              </label>
              @endforeach
            </div>
          </div>
          </div>
          @endforeach
          <!-- ./tag-group -->

          <div class="tag-group">
            <p class="title">
              <span class="filter-title show_filter_content">Brand <span class="pull-right"><i class="fa fa-minus"></i></span></span>
            </p>
            <div class="filter-content">
              <div class="checkbox">
              @foreach($brands as $brand)
              <label for="brands">
                <input name="brands[]" type="checkbox" value="{{$brand->id}}">
                {{$brand->title}}
              </label>
              @endforeach
            </div>
          </div>
          </div>
          <!-- ./tag-group -->

          <div class="tag-group">
            <p class="title">
              <span class="filter-title show_filter_content">Price Range <span class="pull-right"><i class="fa fa-minus"></i></span></span>
            </p>
            <div class="row filter-content">
              <div class="col-md-6">
                <div class="form-group">
                  <label for="min_price" hidden>Min</label>
                  <input type="text" name="min_price" class="form-control" placeholder="Rp Min">
                </div>
              </div>
              <div class="col-md-6">
                <div class="form-group">
                  <label for="max_price" hidden>Max</label>
                  <input type="text" name="max_price" class="form-control" placeholder="Rp Max">
                </div>
              </div>
            </div>
          </div>
          <!-- tag-group -->

          <div class="text-center mt-20">
            <button type="submit" class="btn btn-danger">TERPAKAN</button>
          </div>

        </div><!-- ./filter-tag-group -->
      </div><!-- ./sidebar-content -->
    </form>

and this is my route:

Route::post('/advanced-search', 'frontend\SearchController@filter')->name('advancesearch');

finally my function code is:

public function advancedsearch(Request $request) {
        $brands = Brand::all(); // uses for other part of the page. (not related to search function)
        $options = Option::all(); // uses for other part of the page. (not related to search function)
        $suboptions = DB::table('product_suboption'); // where my product_id and subopyion_id saves

        //search function
        $products = Product::where(function($query){
            //getting inputs
            $suboptions2 = Input::has('suboptions') ? Input::get('suboptions') : [];
            $min_price = Input::has('min_price') ? Input::get('min_price') : null;
            $max_price = Input::has('max_price') ? Input::get('max_price') : null;
            $brands2 = Input::has('brands') ? Input::get('brands') : [];

            //returning results
            $query->where('price','>=',$min_price)
                    ->where('price','<=',$max_price);
            })->get();

        return view('front.advancesearch', compact('products', 'brands', 'options'));
    }

My models relations:

product model:

public function options(){
     return $this->belongsToMany(Option::class);
  }
  public function suboptions(){
     return $this->belongsToMany(Suboption::class, 'product_suboption', 'product_id', 'suboption_id');
  }
public function brand(){
     return $this->belongsTo(Brand::class);
  }

Option model:

public function suboptions(){
     return $this->hasMany(Suboption::class, 'option_id');
  }

  public function products(){
     return $this->belongsToMany(Product::class);
  }

Suboption model:

public function option(){
     return $this->belongsTo(Option::class, 'option_id');
  }

  public function products(){
     return $this->belongsToMany(Product::class);
  }

Brand model:

public function products(){
     return $this->hasMany(Product::class);
}

note

My brands search is coming from products table where I have column brand_id for each product.

BUT

My suboptions come from 3rd table named product_suboption (as you see in my models codes) where i save product_id and suboption_id.


回答1:


This is just to give an idea. You can use a multiple ->where() and eager loading ->with() for your query. Take a look with this query below:

$products = Product::where('price', '>=', $min_price) // you get the max and min price 
        ->where('id', '<=', $max_price)->select('id')
        ->with([
            "brand" => function ($query) {
                $query->whereIn('id', $brand_ids); // [1, 2, 3,...]
            },
            "specifications" => function ($query) {
                $query->where('some_column', '=', 'possible-value'); // single condition
            },
            "specifications.subspecifications" => function ($query) {
                $query->where([
                    'some_column' => 'possible-value',
                    'another_column' => 'possible-value'
                ]); // you can also pass arrays of condition
            }
        ])->get(); // This will return the products with the price set by the user
                   // Since we're just using ->with(), this will also return those products
                   // that doesn't match the other criteria specifications) so we 
                   // still need to filter it.

Finally, you can filter the products which matches the specifications, - the product with an empty specifications means this product does not match the criteria, therefore we'll have to remove it from the collection.

$filtered =  $products->filter(function ($product, $key) {
    return count($product->brand) > 0 && count($product->specifications) > 0;
    // add your other boolean conditions here
});

dd($filtered->toArray()); // your filtered products to return



回答2:


You can use laravel orWhere and orWhereHas to get results separately and all at once, let's say you do not select min_price and max_price but you have selected brand then all products with this brnad should be return, your query will look like this

$products = Product::orWhere('price','>=',$min_price)
->orWhere('price','<=',$max_price)
->orWhereHas('brand',function($query){
    $query->whereIn('id', $brand_ids);
})
->orWhereHas('suboptions',function($query){
    $query->whereIn('id', $suboptions_ids);
})
->orWhereHas('subspecifications',function($query){
    $query->whereIn('id', $subspecifications_ids);
})->get(); 

$products will have products collection If any of the condition stated in above query matched.

Hope this helps.




回答3:


Here's how I'd do it. Note the use of when for simplifying optional where conditions (no need to set variables either), and the closure for constraining both the whereHas and the with (if you want to eager load the relationships).

$products = Product::query()
    ->when($request->min_price, function ($query, $min_price) {
        return $query->where('price', '>=', $min_price);
    })
    ->when($request->max_price, function ($query, $max_price) {
        return $query->where('price', '<=', $max_price);
    })
    ->when($request->suboptions, function ($query, $suboptions) {
        $suboptionsConstraint = function ($q) use ($suboptions) {
            return $q->whereIn('id', $suboptions);
        };
        return $query->whereHas('suboptions', $suboptionsContraint)
            ->with(['suboptions' => $suboptionsContraint]);
    })
    ->when($request->brands, function ($query, $brands) {
        $brandsConstraint = function ($q) use ($brands) {
            return $q->whereIn('id', $brands);
        };
        return $query->whereHas('brands', $brandsConstraint)
            ->with(['brands' => $brandsConstraint]);
    });



回答4:


I suggest tu use each separeted and its help you to feature easaly manupulate code

as your typical condition your sub_option come from third table last relation ship is used.

 if(count($request['suboptions'])) {

         $product->whereHas('options',function($options) use ($request) {

                   $options->whereHas('suboptions',function($suboption)use($request) {

                         $suboption->whereIn('id',$request['suboptions']);
                  });
         }); 
 }

for min price max price i assume your price in procuct table

   if(! empty($request['min_price'])) {

          $product->where('price','>=',$request['min_price']);
    }

 if(! empty($request['max_price'])) {

          $product->where('price','<=',$request['max_price']);
    }

for brand as you say brand_id column in product table then

   if(count($request['brands'])) {

          $product->whereIn('brand_id',$request['brands']);
    } 



回答5:


I suggest a different approach.

On your controller, change it to this:

public function advancedsearch(Request $request) {

$suboptions2 = request->suboptions ? request->suboptions : null;
$min_price = request->min_price ? request->min_price : null;
$max_price = request->max_price ? request->max_price : null;
$brands2 = request->brands ? request->brands : null;

$query = Product::select('field_1', 'field_2', 'field_3')
->join('brands as b', 'b.id', '=', 'products.brand_id')
...(others joins);

// here we do the search query
if($suboptions2){
    $query->where('suboptions_field', '=', $suboptions);
}

if($min_price && $max_price){
    $query->where(function($q2) {
                $q2->where('price', '>=', $min_price)
                    ->where('price', '<=', $max_price)
            });

}

if($brands2){
    $query->where('products.brand_id', '=', $brands2);
}

// others queries

// finish it with this
$query->get();

return view('front.advancesearch', compact('products', 'brands', 'options'));

I find doing it this way is very useful because it can be really easy to implement additional queries.




回答6:


This is the method I use to search using laravel eloquent with multiple input:

$input = Input::all(); //group all the inputs into single array
$product = Product::with('options','suboptions','brand');

//looping through your input to filter your product result
foreach ($input as $key => $value)
{
    if ($value!='') {
       if ($key == "max_price")
            $product = $product->where('price','<=', $value);
       elseif ($key == "min_price")
            $product = $product->where('price','>=', $value);
       elseif ($key == "brands")
            $product = $product->whereIn('brand_id', $value); //assuming that your Input::get('brands') is in array format
       elseif ($key == "suboptions")
            $product = $product->whereIn('suboption_id', $value);
    }
}
$product = $product->get();

The method above will return all products if no input is submitted, and will filter the result based on the input if available, on top of this it's also a good practice to sanitize your inputs with validations before proceeding with the query




回答7:


SOLVED

After weeks of playing with codes finally I came to the right results for myself (in my case it works this way for others maybe works with other suggested answers)

public function advancedsearch(Request $request) {
    $options = Option::all();
    $brands = Brand::all();
    $brandss = Input::has('brands') ? Input::get('brands') : [];
    $suboption = Input::has('suboptions') ? (int)Input::get('suboptions') : [];
    $min_price = Input::has('min_price') ? (int)Input::get('min_price') : null;
    $max_price = Input::has('max_price') ? (int)Input::get('max_price') : null;

    //codes
    if(count($request['suboptions'])){
      $products = DB::table('products')
      ->join('product_suboption', function ($join) {
        $suboption = Input::has('suboptions') ? Input::get('suboptions') : [];
            $join->on('products.id', '=', 'product_suboption.product_id')
                 ->where('product_suboption.suboption_id', '=', $suboption);
        })
      ->paginate(12);
    }

    elseif(count($request['brands'])){
      $products = DB::table('products')
      ->whereIn('products.brand_id', $brandss)
      ->paginate(12);
    }

    elseif(count($request['min_price']) && count($request['max_price'])){
      $products = DB::table('products')
      ->whereBetween('price', [$min_price, $max_price])
      ->paginate(12);
    }


    return view('front.advancesearch', compact('products', 'brands', 'options'));
    }

NOTE: most of my pricing issues solved with (int) as you see in my codes (int)Input::get('min_price') and (int)Input::get('max_price').

Special thanks to Ravindra Bhanderi for his count($request[''] suggestion.



来源:https://stackoverflow.com/questions/48918176/laravel-advanced-search-query-fix

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!