Nick
Nick

Reputation: 2980

Filtering data in laravel and handling the views

I have a tv show netflix-esque project I'm building where I have a Shows page which I want to filter on format. Each show contains episodes which can have a tv, dvd and bd format.

Currently I'm filtering using separate routes and controllers which extend the base ShowsController.

Route::get('shows/view/{type}', ['as' => 'shows.viewtype', 'uses' => 'ShowsController@viewType',]);
Route::get('shows/bluray',['as' => 'shows.bluray','uses' => 'ShowsBlurayController@index']);
Route::get('shows/dvd',['as' => 'shows.dvd','uses' => 'ShowsDVDController@index']);
Route::get('shows/tv',['as' => 'shows.tv','uses' => 'ShowsTVController@index']);

Example of one of the format controllers

class ShowsBlurayController extends ShowsController
{
    public function index()
    {
        // Set user state for browsing bluray
        Session::push('user.showtype', 'bluray');

        $shows = $this->show->getBlurayPaginated(16);

        return $this->getIndexView(compact('shows'));
    }
}

I use the getIndexView() method (in the ShowsController) to determine one of 2 available views: poster and list.

public function getIndexView($shows)
{
    $viewType = get_session_or_cookie('show_viewtype', 'list');
    if ($viewType == 'posters') {
        return View::make('shows.index', $shows)
            ->nest('showsView', 'shows.partials.posters', $shows);
    } else {
        return View::make('shows.index', $shows)
            ->nest('showsView', 'shows.partials.list', $shows);
    }
}

The shows are filtered based on the episodes:

public function getBlurayPaginated($perPage)
{
    return $this->getByFormat('BD')->with('tagged')->paginate($perPage);
}
private function getByFormat($format)
{
    return $this->show->whereHas('episodes', function ($q) use ($format) {
        $q->whereHas('format', function ($q) use ($format) {
            $q->where('format', '=', $format);
        });
    });
}

The problem is that I want to do this in a clean way. When a user selects a format, that filter will be applied. Currently all of this is kind of scattered across controllers and doesn't quite make sense.

I also thought of doing something like this in the routes.php:

Route::get('shows/format/{format}',['as' => 'shows.format','uses' => 'ShowsController@index']);

And then handle the filtering in the index, but that also seems a weird place to do that.

This approach does work, but I don't want to screw myself later on with it. I'm planning a simple search which should take the filter into account.

In other words, how can I organize the code in such a way that getting data from the database will take the filter into account which has been set? (Session states maybe?)

Upvotes: 4

Views: 14404

Answers (1)

Jeff Lambert
Jeff Lambert

Reputation: 24661

Route::get('shows/format/{format}',[
    'as' => 'shows.format',
    'uses' => 'ShowsController@index'
]);

I think you're on the right track here. I would go so far as to produce a factory and inject it into the controller. The purpose of this factory is to construct a formatter that will supply your view with the correct data:

// ShowController
public function __construct(ShowFormatFactory $factory, ShowRepository $shows)
{
    $this->factory = $factory;
    // NB: using a repository here just for illustrative purposes.
    $this->shows = $shows;
}

public function index($format = null) 
{
    $formatter = $this->factory->make($format);

    return View::make('shows.index', [
        'formatter' => $formatter,
        'shows' => $this->shows->all(),
    ]);
}

// ShowFormatFactory
class ShowFormatFactory
{
    public function make($format)
    {
        switch($format) {
            case 'blueray':
                return new BluerayFormat(); break;

            case 'dvd': /* Fallthrough for default option */
            default:
                return new BluerayFormat(); break;
        }
    }
}

// ShowFormatInterface
interface ShowFormatInterface
{
    public function format(Show $show);
}

// BluerayFormat
class BluerayFormat implements ShowFormatInterface
{
    public function format(Show $show)
    {
        return $show->blueray_format;
    }
}

Then in your view, since you are guaranteed to have an object that will provide you the format requested for a given show, just call it:

@foreach($shows as $show)
<div class="show">
    Chosen Format: {{ $formatter->format($show) }}
</div>
@endforeach

This solution is testable and extensible will allow you to add other formats later on. If you do, you would need to add a discrete case statement in the factory for each different format, as well as write a rather slim ~5-7 line class to support the new format.

Upvotes: 2

Related Questions