Refine will be launching soon 🎉 Sign up for the early access list to stay up to date.
Refine for Laravel
A package by Hammerstone

Stabilizing Filters

Hammerstone Refine has not launched yet. Sign up for the early access list to stay up to date!

Here we'll go over the process of stabilizing filters so that they can be viewed, reused, or edited at a later time. We use the term "stabilization" because after a filter goes through the stabilization process, you are returned a string or an ID that will always and forever reference a specific filter in a specific state.

Stabilization is similar to serializing or encoding, in that a filter with a state $x will always stabilize to an ID $y, and an ID $y will always resolve to a filter with state $x. It is bi-directional.

Persisting? Serializing?

Why do we call it stabilizing and not persisting? Persisting implies that the data will be stored somewhere, which is not necessarily true of all of Refine's stabilization drivers. The Database Stabilizer persists the data, but the URL Encoder does not.

Why don't we call it serializing? Serializing usually implies that all of the data is still present in the resulting value. That's not true of all of Refine's stabilization drivers either! The result of the URL Encoder contains all the data, but the Database Stabilizer returns a small pointer to a record in the database.

We landed on the term "stabilization" because it encompasses both concepts.

Refine Defaults

By default, there is no automatic stabilization strategy. This means that the only place the state of a current condition is stored on the client-side and there is no way to directly navigate to a filter. This is perfectly reasonable and may be your preference. There is nothing wrong with that and totally depends on your application. If you don't need to filters to be directly navigable, then you might not need stabilization at all!

On the other hand, if your users expect to be able to bookmark certain filters, or grab the link and send it to a colleague, then you'll need a way to directly navigate to a filter. In that case, you would need some sort of stabilization, i.e. a way to persist the state of a filter.

Manual vs. Automatic

Stabilization can be a manual process, an automatic one, or you can take advantage of both simultaneously if your situation calls for it!

When using manual stabilization, nothing at all will happen (in terms of stabilization) unless you write code the to do it. In an application where users are given the opportunity to play around with filters until they find the perfect one and then hit a "Save" button, it may make most sense to put off stabilization until that point, at which you'd save it to the database.

If you decide that you want to stabilize the filter automatically, every time it's run, then you should use automatic stabilization. When you turn automatic stabilization on, every filter will come back from the server with a stable_id that you can put in the URL or otherwise store.

Using Automatic Stabilization

You'll need to do two things to enable automatic stabilization. The first is override the automaticallyStabilize method to return true.

1// In your filter...
2public function automaticallyStabilize()
3{
4 return true;
5}

The next thing you'll need to do is inform the package which stabilizer you'd like to use. You can do so globally by calling defaultStableIdGenerator on the Filter class:

1// In a service provider...
2Filter::defaultStableIdGenerator(UrlEncodedStabilizer::class);
Code highlighting powered by torchlight.dev, a Hammerstone product.

This will set the default stabilizer for all filters.

You can also implement a method in a particular filter:

1// In your filter...
2protected static function automaticStableIdGenerator()
3{
4 return CacheStabilizer::class;
5}

This will override the default, if there was one.

We have provided several stabilizers out of the box, which you can see below.

Manual Stabilization

If you'd prefer to only stabilize filters manually, you may do so by calling toStableId on a filter at any time. You'll need to pass in the Stabilizer that you want to use.

1// In a controller...
2 
3public function store(Request $request)
4{
5 // Create a filter from a request
6 $id = EmployeeFilter::make($request->blueprint)
7 // stabilize via a fully qualified class name
8 ->toStableId(UrlEncodedStabilizer::class)
9 
10 // Create a filter from a request
11 $id = EmployeeFilter::make($request->blueprint)
12 // stabilize via an instantiated class
13 ->toStableId(new UrlEncodedStabilizer)
14}

Creating a Filter From an ID

All stable IDs store a reference to the correct class, so you can call Filter::fromStableId($id) on the base class and you'll receive back an instance of the correct subclass.

For example, in a controller you could do the following:

1class EmployeeController extends Controller
2{
3 public function index(Request $request)
4 {
5 // Instantiate a filter from a stable ID
6 return Filter::fromStableId($request->filter_id)->getQuery()->paginate();
7 }
8}

Creating From a Null Stable ID

In the example above, you'd have to be sure that $request->filter_id was populated, because without knowing the state of the saved filter, we won't know which class to instantiate.

If you'd like to instantiate from a potentially null stable ID, you can call fromStableId on the class you'd like to instantiate instead of the base class.

1// Possibly null!
2$id = $request->filter_id;
3 
4// Filter is an abstract class, so this
5// will throw an error if $id is null.
6Filter::fromStableId($id);
7 
8// This will give you a blank EmployeeFilter if $id is null.
9EmployeeFilter::fromStableId($id);

Provided Stabilizers

We have provided 5 stabilizers out of the box for you. Each one does something a little bit different that makes sense in different situations. You can read more about them on their individual pages.

  • Database - Persists the state to the database.
  • UrlEncoded - Encodes the filter's state but doesn't persist it anywhere. Totally self-contained.
  • Hybrid - Combination of both the UrlEncoded and Database methods.
  • Cache - Stores the state to the cache, is only semi-stable due to nature of cache.
  • Session - Stores the state to the session, is only semi-stable due to nature of session.

Writing Your Own Stabilizer

All stabilizers must implement the Stabilizer interface, which enforces two methods: fromStableId and toStableId.

As long as your class conforms to that interface, you are free to implement any custom logic that you like.