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 ]);}
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]); }}
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.