Refine for Laravel
A package by Hammerstone

Extending Standard Conditions

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

No matter how configurable the standard conditions may be, there are still going to be times when you need to do something custom for your application.

We've put a great amount of effort into giving you as many places to modify the behavior of the standard conditions.

Psst... You can also create your own custom condition entirely from scratch.

The Boot Method

Every condition has a boot method that can be used to do some kind of post-constructor initializing. We often use that internally to add validation rules and ensure that the condition is configured correctly. Below is the boot method from the Date Condition:

protected function boot()
{
// Add validation rules.
$this->addRules([
'date1' => [
'nullable',
'date'
],
'date2' => [
'nullable',
'date'
],
]);
 
// Ensure the developer has set timezones up correctly.
$this->addEnsurance([$this, 'ensureTimezones']);
}

You may use it to do any number of things, like hide clauses:

protected function boot()
{
parent::boot();
 
$this->withoutClauses([
self::CLAUSE_LESS_THAN,
self::CLAUSE_GREATER_THAN
]);
}
Code highlighting powered by torchlight.dev, a Hammerstone product.

or add meta:

protected function boot()
{
parent::boot();
 
$this->withMeta([
'me' => Auth::id()
]);
}

Booting Traits

Every trait that is added to a condition is given the opportunity to boot itself. If you have a set of common functionality that you've factored out to a trait called RequiresConfirmation, then you can have a method called bootRequiresConfirmation and do any setup work there:

trait RequiresConfirmation
{
// This will be automatically called while the
// condition is booting.
protected function bootRequiresConfirmation()
{
$this->addRules([
'confirmed' => [
'required',
'accepted'
]
]);
}
}

You can take a look at the bootHasClauses method in the HasClauses trait to see some of this in action:

protected function bootHasClauses()
{
// ...
 
$this->beforeValidate([$this, 'beforeValidationOfClause']);
}

In this case, the HasClauses trait is hooking into the beforeValidate method to potentially add extra rules based on which clause the user has chosen. If the user chooses a clause that requires extra fields, then we can make those fields required right before we run validation.

Modifying the Query

If you'd like to modify how the user's input is being applied to the query, you may extend the applyCondition method and do anything that you need. Often times, however, you only want to modify the application of a particular clause. Each condition handles application differently, but many of them break out the application into several small methods.

For example, if you wanted to use MySQL's fulltext search instead of the simple LIKE matching, you could do so by extending the Text Condition and overriding the applyClauseContains method:

class TextCondition extends BaseTextCondition
{
protected function applyClauseContains($query, $value)
{
// Use fulltext search for the `contains` clause only.
$query->whereRaw("MATCH ($this->attribute) AGAINST (?)" , [$value]);
}
}

You'll have to take a look at each of the individual conditions to see where the best place to hook in might be.

Custom Clauses

If you are adding custom clauses to a condition, you'll need to specify how those clauses get applied to the query. The base condition has a method called applyCustomClause that you can override to handle your custom clause application. By default this method just throws an exception alerting you that a custom clause is unhandled.

Let's take the fulltext example from above and rework it as a custom clause.

class TextCondition extends BaseTextCondition
{
const CLAUSE_SIMILAR = 'similar';
 
protected function customClauses()
{
return [
Clause::make(self::CLAUSE_SIMILAR, 'Is Similar To')
->rules([
'value' => ['required']
])
];
}
}

We've added our custom clause and a validation rule, but now we need to handle the application itself.

class TextCondition extends BaseTextCondition
{
const CLAUSE_SIMILAR = 'similar';
 
protected function customClauses()
{
return [
Clause::make(self::CLAUSE_SIMILAR, 'Is Similar To')
->rules([
'value' => ['required']
])
];
}
 
public function applyCustomClause($query, $clause, $input)
{
$query->whereRaw("MATCH ($this->attribute) AGAINST (?)" , [$value]);
}
}
Code highlighting powered by torchlight.dev, a Hammerstone product.

Now your users are able to use fulltext search when they choose the "Is Similar To" clause.

The first parameter is the currently built query, the second is the clause that the user has chosen, and the third is an array of input.

In the $input array will always include a raw key that contains the user's input exactly as we received it, in case you need to do anything with it. In many cases, the condition that you're extending has done some kind of work cleaning and standardizing the input, so we'll pass that cleaned input to you as well under various keys.

Helpful Extension Methods

There are a number of methods designed to help you extend the standard conditions.

addEnsurance

Add a callback to ensure that the condition has been configured correctly. Check for options or properties being set, etc. From HasClauses:

protected function bootHasClauses()
{
// Add a callback to ensure that the clauses are
// all configured correctly.
$this->addEnsurance([$this, 'ensureClauses']);
}

addRules

Add validation rules. Your rules can be callables if they depend configuration settings. For example, here is the rule that the HasClauses trait adds:

protected function bootHasClauses()
{
$this->addRules([
'clause' => [
'required',
// Lazily evaluate the clauses to give the developer time to
// exclude certain clauses. By passing a closure, this rule
// will be resolved only when it's time to validate.
function () {
return Rule::in(Arr::pluck($this->getClauses(), 'id'));
}
]
]);
}

withMeta

Add arbitrary meta to a condition. Your meta value can be a callable for lazy evaluation.

protected function bootHasClauses()
{
// Lazily add the clauses to the meta object
// for the same reason as mentioned above.
$this->withMeta('clauses', [$this, 'getClauses']);
}

beforeValidate

Add a callback to be run before any validation occurs. This callback will receive the user's input as its only argument.

modifyUserInput

This is a method in the base condition that you can extend to modify the user's input in any way you want. By default it just returns exactly what it was given.