Baryla's avatar

Laravel search routing

Hey guys,

I'm developing an application that has some basic search. When the user navigates to /search, they will see some default search content like the most popular places and when they go to /search?city=whatever, all the necessary info will be shown for "whatever".

This is my code so far:

Controller:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SearchController extends Controller {

   public function search(Request $request) {

      if(count($request->all()) > 0) {

         // Display content based on the search query

      } else {

         // Display default content without search query

      }

   }

}

Route:

Route::get('/search', 'SearchController@search');

It basically checks if there are requests and if there are, display the relevant content, if not, display the default content.

My question is if this is a good approach to do this? Maybe a better approach would be with a wildcard in the route?

0 likes
5 replies
bobbybouwmann's avatar

Well it depends.. as always :P

If your view and data is the same for both displaying all or based on the query your current approach would be fine. You would have something like this

// Let's say you have a list of articles
public function search(Request $request)
{
    if (count($request->all()) > 0) {
        $articles = $this->doSearchQuery($request);
    } else {
        $articles = $this->doAllQuery();
    }

    return view('search', compact('articles'));
}

This lesson might be useful as well: https://laracasts.com/lessons/search-essentials

Zurik.Ludg's avatar

Just to share.. I was working on the same feature that you had and my solution that I used to display preset contents when searches are being made OR not are to implement the following in the view blade file.

if ($request->isMethod('post')) {
    //
}
1 like
Baryla's avatar

@bobbybouwmann Good call with the lesson! I'll check it out. Regarding your code, I do pass the data to my view in similar way to what you proposed but I just didn't include the rest of the code because I thought it wasn't necessary. So I guess, what I had, is not a bad approach for what I need.

@Zurik.Ludg I like it! However, I am using the get method on the form because I want the url to have the search contents.

Thanks for the tip though!

jbloomstrom's avatar

If you find your controller becoming bloated, you can choose to abstract out the particulars of the search behavior to its own Request method - similar to a query object.

This recent video explains the pattern better than I can.

tobiasj's avatar

Why not simply use a scopeon your resource model?

Something like function scopeFilter($query, $filters) {...}, then you could simply pass$request->all() to the scope and dynamically build the query (or even use other scopes).

In your controller you'd then just do something like this:

public function search(Request $request)
{
    $data = MyResourceModel::filter( $request->all() );

    return view('search', compact('data'));
}

or you could even make it more dynamic by extracting the scopeFilter to a trait filterable and add it to the base model:

trait Filterable
{
    public function scopeFilter($query, $filters)
    {
        // ...
    }
}

in your controller:

public function search(Request $request)
{
    // Validate that a Resource was given

    // Resolve the Resource
    $resource = 'Namespace\\To\\Resources' . ucfirst($request->input('resource'));
    $data = App::make($resource)::filter( $request->all() );

    return view('search', compact('data'));
}

Note: there might be some more elegant ways on the dynamic solution but this one just came up in my mind.

The benefit of using a scope would be, that if no filters are passed it simply returns the same query builder as before adding the scope. So if you'd do something like

Resource::latest()->filter($request->all)->get();

while no filters are given, it will be the same query as if you'd just do:

Resource::latest()->get();

Please or to participate in this conversation.