Refine for Laravel
A package by Hammerstone

Attributes

Refine is a paid package. To purchase, head to hammerstone.dev.

Almost all of the standard conditions are applied against attributes, which are usually columns in your database. The only conditions that is not applied against an attribute is the relationship condition, which queries against the count of a related model.

All of the conditions that operate on attributes use a UsesAttributes trait, which exposes an attribute method that you can use to set the attribute's value to either a column name or a raw expression.

Setting an Attribute

By default, when you construct a condition that uses attributes, the attribute is optimistically set to the same value as the id.

For example, this:

DateCondition::make('published_at');

is exactly the same as:

DateCondition::make('published_at')->attribute('published_at');
Code highlighting powered by torchlight.dev, a Hammerstone product.

If you are storing your filters, it is vitally important that your condition's ID never change. If the ID were to change in your code but not in your stored state, we would no longer be able to match the stored data to the condition.

For clarity, you may decide to always explicitly set the attribute using the attribute method, even when the ID and attribute match. That is a perfectly reasonable decision!

Relationship Attributes

There may be times when you want to allow your users to query attributes on related models, instead of the model upon which the filter is based.

We take of this common scenario for you. Normally when you set an attribute, it's understood to be an attribute on the base model. But if you pass and attribute in the form of {relation}.{attribute} then we'll handle the subquery required to query the relationship.

In the scenario where your filter is an Employee filter, and there is a Manager relationship, you can easily allow your users to query on the manager's name by setting your condition up as follows:

TextCondition::make('manager_name')->attribute('manager.name');

Every type of Laravel relationship is supported and any condition that uses attributes supports querying attributes on related models. Infinitely nested relationships are also supported, although you'll need to weigh the performance considerations of multiple levels of nesting.

The following is totally valid:

TextCondition::make('regional_manager_name')->attribute('manager.manager.name');

And if your user searches for employees who's regional manager is name "Michael Scott", something equivalent to the following would be applied to your query:

$query->whereHas('manager', function($subquery) {
$subquery->whereHas('manager', function($subquery) {
$subquery->where('name', 'Michael Scott');
});
});

Note: we don't always rely on Laravel's whereHas method because under the hood that compiles down to where exists in SQL, creating a dependent subquery, which can cause a huge performance bottleneck. For some types of relationships, we instead create a where in with a subquery, which runs much much faster. All of this is totally transparent to you the developer.

Preventing Querying Relationships

Sometimes when your initial query joins a table in, you may have an attribute that requires a fully qualified {table}.{column} identifier and therefore don't want Hammerstone to look for a relationship called table on the model.

If that's the case, you can prevent querying on relationships by calling attributeIsNotRelationship.

TextCondition::make('manager_name')
->attribute('t1.name')
->attributeIsNotRelationship();

JSON Attributes

If you attribute is a key in a JSON column, you can use the standard Laravel notation to denote that:

TextCondition::make('nested_json')
->attribute('column->key');

You can read more about the -> operator in Laravel docs.

Raw Expression Attributes

In Laravel, you're able to use the DB facade to query raw expressions instead of just attributes.

For example, if you wanted to find employees with summer birthdays, you could do that in vanilla Laravel using a raw expression:

$query->where(DB::raw(' MONTH(birthday) BETWEEN 6 AND 8 '));
Code highlighting powered by torchlight.dev, a Hammerstone product.

This would give you employees with birthdays between June (6) and August (8).

If you'd like to expose that functionality to your users in a filter, you can do so by setting the attribute to a raw expression using the rawAttribute method or by passing in an instance of Expression:

BooleanCondition::make('summer_birthday')
->rawAttribute(' MONTH(birthday) BETWEEN 6 AND 8 ');
 
// Or...
 
BooleanCondition::make('summer_birthday')
->attribute(DB::raw(' MONTH(birthday) BETWEEN 6 AND 8 '));

Now the end user can choose Summer Birthday: Is True or Is False.

Any condition that uses attributes can accept a DB::raw expression instead of a string.