I am lucky enough to get to work on a large Laravel 5 full time in my day job, with some awesome developers.I recently came up with what is, in my humble opinion, an elegant and simple solution for what we all initiallythought was quite a complex problem. If you’ve used this trick before,or know how to improve on it, please let me know – I’d love to hear from you!

The problem

We are working on the latest iteration of our product, which is a re-write in Laravel 5 using PJAX for the UIinteraction. The old version of the product used Laravel 3 and AngularJS. This means that we are using very differenttechnologies for all parts of the stack, and as you can probably imagine, some things that were easyin the old version now have added complexity. (That said, the new stack also makes some things a lot easier!)

We needed to implement persistence in our record list filtering behaviour. The filter system is being poweredusing URL parameters that are appended to / updated when you change the filter options – however this does notpersist when you leave the page and come back to it. We all agreed it was going to be tricky, given our design,and I was the unlucky developer whose queue it ended up at the top of.

Possible options

I thought for a while about possible solutions – maybe hijacking the events in PJAX and remembering the URLparameters in the browser, and manually injecting them, but I really don’t like Javascript so I tried tofind a solution that would approach the problem on the PHP side. I contemplated possible options ofmoving the filters out of URL parameters and persisting them in the session, but that gets messy:do they need to be POST‘ed to the server? How do you share links with specific filter options?How should they be stored? Should the common options work across different lists?

No, I had to stick with URL parameters.

My solution

After a bit of thought, I remembered the middleware design that Laravel 5 uses. I realised that this isthe perfect place to inject logic relating to a request before the request is actually processed.

The solution is elegant and simple:

<?php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;

class ParameterPersistence
{
    public function handle(Request $request, Closure $next)
    {
        // Unique session key based on class name and route name
        $key = __CLASS__.'|'.$request->route()->getName();
        
        // Retrieve all request parameters
        $parameters = $request->all();

        // IF  no request parameters found
        // AND the session key exists (i.e. an previous URL has been saved)
        if (!count($parameters) && Session::has($key)) {
            // THEN redirect to the saved URL
            return redirect(Session::get($key));
        }

        // IF there are request parameters
        if (count($parameters)) {
            // THEN save them in the session
            Session::put($key, $request->fullUrl());
        }

        // Process and return the request
        return $next($request);
    }
}

The way it works is simple.

First, if you access the page initially without any parameters before a URL has been saved(http://example.com/users), thenit will do nothing. The request will be processed and nothing will be stored in the session.

Next, if you access the page with URL parameters, for example by clicking on a sort order link(http://example.com/users?sort=name&order=asc),then it will save the requested URL in the session (under a unique key for that specific page) and thenthe request will be processed as per normal.

Finally, if you access the page without any URL parameters but after a URL has been saved(http://example.com/users), then it will redirect you to the last saved URL that contained parameters(http://example.com/users?sort=name&order=asc).

This process is completely transparent to the application itself, as it all happens in the middlewareusing redirects, before the application logic is initiated. This makes the redirects very fast, and removesany complexity around persistence in the filter handling logic.

Things to note

  1. It will only redirect when the request has no parameters (i.e. http://example.com/users) andhas a previously saved URL. This means that any time a parameter is added or removed(without clearing all parameters), it will save the new request URL and render the page.
  2. It will only save requests that have parameters. If there are no parameters, and no saved URLs, it will donothing. This saves data being needlessly stored and potential redirect loops.
  3. It should only be included on pages that need it – it will cause issues with other parametersthat should not be automatically remembered. (Something like ?delete=true would be very bad!)
  4. If you are using a load balancer, or for some reason cannot use absolute URLs, you will need to replace$request->fullUrl() with something more appropriate. For example, this will give you a relative URL:$request->path().'?'.$request->getQueryString().
  5. We use named routes, so the session key uses the route name. If you don’t use named routes, you will need tochange the key generation to use whatever is unique about the request for that record list page.

I hope you found that useful! 🙂