Laravel's conditionable trait can help make code more fluent by allowing you to conditionally invoke methods within a callback. This article gives you everything you need to know to start using it.
Laravel is packed full of traits within various classes around the framework. Conditionable is no different and provides a way to chain on either a when
or unless
method to invoke another method under a condition.
If that sounds confusing, let's compare two examples.
The first is an example of what you might do without the Conditionable
trait:
$something = new Something();
if ($someCondition) {
$something->doSomething();
}
If the Conditionable
trait was applied to our Something
class, we could do this instead:
$something = new Something();
$something->when($someCondition, function (Something $something) {
$something->doSomething();
});
I know what you're going to say. Surely this isn't better? There's more code, and it doesn't look as clear! And in this example, you'd be correct.
Instead, let's see how this looks on something like Laravel's query builder. While this isn't the best example of ordering, let's say we want to orderBy
based on a query string value, but only when it exists. Then, we want to return the response.
$users = User::query();
if ($request->has('orderBy')) {
$users->orderBy($request->get('orderBy'), 'desc');
}
return $users->paginate(10);
Behind the scenes, the Builder
contains the Conditionable
trait (one of many places it exists). So, we can tidy this up into what is effectively one line of code:
return User::query()
->when($request->has('orderBy'), function (Builder $builder) use ($request) {
$builder->orderBy($request->get('orderBy'), 'desc');
})
->paginate(10);
While it's still subjective whether this is cleaner, it avoids repeating multiple IF statements. If things got more complex than this, it could look clearer.
Let's actually examine what's happening here:
when
method is called, passing in the condition and a callbackFor the example above (filtering and ordering with a query string), and in this scenario, we can actually tidy things up further since we're dealing with a callback.
If we created an invokable class like this:
class OrderFilter
{
public function __construct(protected Request $request) {}
public function __invoke(Builder $builder)
{
return $builder->orderBy($this->request->get('orderBy'), 'desc');
}
}
We could then make when
more readable and duplicatable like this:
return User::query()
->when($request->has('orderBy'), new OrderFilter($request))
->paginate(10);
So, for multiple filters, we may end up with something like this:
return User::query()
->when($request->has('orderBy'), new OrderFilter($request))
->when($request->has('notVerified'), new NotVerifiedFilter($request))
->when($request->has('softDeleted'), new SoftDeletedFilter($request))
->paginate(10);
You'll agree that's much cleaner than a bunch of IF statements within your controllers!
Tip: If you did want to filter like this, I'd recommend Spatie's laravel-query-builder package.
Because Conditionable
is just a single trait, you can easily apply it to your own classes. You'll instantly have access to the when
and unless
methods (which we'll cover next).
use Illuminate\Support\Traits\Conditionable;
class Something
{
use Conditionable;
public function someMethod()
{
// do something
return $this;
}
public function someOtherMethod()
{
// do something else
return $this;
}
}
And here's an example of how you'd now be able to use this:
$something = new Something();
$something
->someMethod()
->when($someCondition, function (Something $something) {
$something->someOtherMethod();
});
As well as a when
method, there's also an opposite... unless
. This allows you to control the flow of method chaining based on what makes sense for your application.
Here's an example of using unless
with the Rule
's Unique
class (which, yep, also contains the Conditionable
trait!):
$request->validate([
'email' => [
'required',
'email',
Rule::unless(
config('users.allow_duplicate_emails'),
fn () => [Rule::unique('users', 'email')]
)
]
]);
In this contrived example, we're allowing users to register with duplicate email addresses as long as we've configured it.
The Conditionable
trait can be really useful in some circumstances to clean up your code. While I haven't listed everywhere it's used, it's used a lot — just take a look at the source whenever you think you might find conditionable functionality helpful to see if the trait is included.
And, of course, since it's just a trait, you're free to use it in your classes as you need to tidy things up.