Refine for Laravel
A package by Hammerstone

Clauses

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

In addition to attributes, a basic building block of conditions is "clauses".

The clause is the part of the condition that links the attribute to the data. Let's break that down a bit.

If you have a condition that allows your users to filter based on employee name, then your end users will end up with filters that include criteria like:

  • Name equals "Aaron"
  • Name starts with "A"
  • Name does not contain "test"

The pattern that you can see here is [attribute] [clause] [value].

@TODO Insert screenshot of frontend with attribute clause and value identified.

In the examples above, the attribute is "Name". The values are "Aaron", "Jo", and "test'. The clauses are the words that link the attributes to the values: "equals", "starts with", and "does not contain".

Default Clauses

Every standard condition that uses clauses defines its own set of reasonable defaults for you to use without having to change anything. However, there may be certain conditions where you don't want particular clauses showing up. We've provided a number of methods to make that easier for you.

Excluding Clauses

If you would like to exclude certain clauses, you can do so by calling the withoutClauses method.

TextCondition::make('name')
->withoutClauses([
TextCondition::CLAUSE_SET,
TextCondition::CLAUSE_NOT_SET
]);

This will prevent the clauses from being sent to the frontend and shown to your end users. This also prevents them from being valid choices on the backend, if your users were to try to sneak them in.

Including Only Certain Clauses

Instead of excluding clauses, if you'd prefer to strictly include specific clauses, the onlyClauses method is available to you.

TextCondition::make('name')
->onlyClauses([
TextCondition::CLAUSE_SET,
TextCondition::CLAUSE_NOT_SET
]);
Code highlighting powered by torchlight.dev, a Hammerstone product.

With the configuration above, your end users would only be able to choose between the two clauses we've explicitly allowed, the Is Set and Is Not Set clauses.

Adding Clauses Back

If you've excluded clauses that you need to later re-add, you can do that with the withClauses method.

$condition = TextCondition::make('name')
->withoutClauses([
TextCondition::CLAUSE_SET,
TextCondition::CLAUSE_NOT_SET
]);
 
// Later on...
 
$condition->withClauses([
TextCondition::CLAUSE_SET,
]);

You can chain withClauses, withoutClauses, and onlyClauses in any order that you want, but whatever you call last takes the highest priority.

Constants

Every standard condition uses clauses, which are represented by class level constants to ease development. We keep the string representations of the clauses as compact as possible so that stabilized filters can be as small as possible.

Any time you need to use a clause, it's recommended that you use the constant:

// This is good...
TextCondition::make('name')
->onlyClauses([
TextCondition::CLAUSE_SET,
TextCondition::CLAUSE_NOT_SET
]);
 
// This is bad...
TextCondition::make('name')
->onlyClauses([
'st',
'nst'
]);

Displays

Every clause has "id" and a "display" attributes. The id is the value that the frontend and backend communicate via, while the display value is the value you can show to your end users.

For example, for the clause TextCondition::CLAUSE_STARTS_WITH the id is 'sw' and the display is 'Starts With' by default.

In many cases, you may want to have different display values than the ones we've provided for you. You can use the remapClauseDisplays method to accomplish that.

TextCondition::make('name')
->remapClauseDisplays([
TextCondition::CLAUSE_STARTS_WITH => 'begins with',
TextCondition::CLAUSE_CONTAINS => 'includes',
]);

In some cases, you may want to remap the displays for every instance of a condition. If your application is not in English, then you would want to change all the displays to the language of your application.

You can accomplish this with the static $defaultClauseDisplayMap.

// In a service provider...
 
TextCondition::$defaultClauseDisplayMap = [
TextCondition::CLAUSE_EQUALS => 'équivaut à',
TextCondition::CLAUSE_DOESNT_EQUAL => 'n\'est pas égal',
TextCondition::CLAUSE_STARTS_WITH => 'commence avec',
TextCondition::CLAUSE_ENDS_WITH => 'se termine par',
TextCondition::CLAUSE_DOESNT_START_WITH => 'ne commence pas par',
TextCondition::CLAUSE_DOESNT_END_WITH => 'ne commence pas par',
TextCondition::CLAUSE_CONTAINS => 'contient',
TextCondition::CLAUSE_DOESNT_CONTAIN => 'ne contient pas',
TextCondition::CLAUSE_SET => 'est réglé',
TextCondition::CLAUSE_NOT_SET => 'n\'est pas défini',
];

Now every TextCondition you use will have French displays instead of the standard English ones.

If you have both a defaultClauseDisplayMap and also call remapClauseDisplays, the latter will take precedence.

Custom Clauses

We've provided a decent number of clauses out of the box for you, but there are undoubtedly going to be times you want to add custom clauses specific to your application.

To add custom clauses, you would first need to create a custom condition. In this example, we're going to add a "Is This Year" clause to the Date Condition.

Every condition that uses clauses has an empty method called customClauses that you can use in your extended class.

class DateCondition extends BaseDateCondition
{
const CLAUSE_THIS_YEAR = 'ty';
 
protected function customClauses()
{
return [
Clause::make(self::CLAUSE_THIS_YEAR, 'Is This Year')
];
}
}

The Clause class is a simple helper class that provides a few quality-of-life methods that you can use.

By extending the Date Condition and populating the custom clauses method, you now have a new clause of "Is This Year" that your users can choose from. Of course you'll need to apply this condition differently, because the Date Condition won't know how to handle this custom condition. (You can read all about extending conditions here.)

In the case of the Date Condition, we're going to hook into the modifyDatesAndClause. We'll first test to make sure we're dealing with a custom clause by checking isCustomClause, if not we'll return what we were given.

If we are dealing with our custom clause, we're going to set date1 to the beginning of the year, date2 to the end, and then change the clause the standard CLAUSE_BETWEEN:

class DateCondition extends BaseDateCondition
{
const CLAUSE_THIS_YEAR = 'ty';
 
protected function customClauses()
{
return [
Clause::make(self::CLAUSE_THIS_YEAR, 'Is This Year')
];
}
 
protected function modifyDatesAndClause($date1, $date2, $clause)
{
if (!$this->isCustomClause($clause)) {
return [$date1, $date2, $clause];
}
 
return [
now()->startOfYear(),
now()->endOfYear(),
DateCondition::CLAUSE_BETWEEN
];
}
}
Code highlighting powered by torchlight.dev, a Hammerstone product.

Each condition handles the application of custom clauses a little bit differently, so you'll have to check the condition you're extending for the best place to hook in.

As a final note: all of your custom clauses work with the standard clause operations, meaning you can reference them in the withClauses, withoutClauses, onlyClauses, remapDisplay, etc. methods.

Clause Validation Rules

If your clause requires custom validation rules, you can add rules that will only be checked when the user has chosen your clause.

Let's say that you are building a custom clause that lets users filter for dates that are within a week of a given date. In that scenario, you'd want to validate that the date was populated.

class DateCondition extends BaseDateCondition
{
const CLAUSE_WITHIN_A_WEEK = 'wtin_week';
 
protected function customClauses()
{
return [
Clause::make(self::CLAUSE_WITHIN_A_WEEK, 'Is Within a Week of')
->rules([
'date1' => ['required']
])
];
}
}

When the user chooses your custom clause, we'll validate that date1 is present. (The base Date Condition also has a rule to validate that date1 is a valid date when it is required.)

If you're just adding required fields, there is a shorthand requiresInputs method you can call:

class DateCondition extends BaseDateCondition
{
const CLAUSE_WITHIN_A_WEEK = 'wtin_week';
 
protected function customClauses()
{
return [
Clause::make(self::CLAUSE_WITHIN_A_WEEK, 'Is Within a Week of')
->requiresInputs('date1')
];
}
}