The Rate Limiter should be one of the most overlooked features in Laravel. For those who are new to the Rate Limiter, the gist of it is simple to understand: to “rate limit” something based on the maximum number of attempts inside a window of time.
The normal and most common procedure to use the Rate Limiter is to, first, check if a key as been attempted too many times. If this key is available, only then we proceed to execute our logic. Finally, we increment the number of attempts along the window of time to “decay”.
This is basically a two-step logic.
The above translates in “If the
foo key has been attempted more than 5 times, return something. Otherwise, execute the logic, and increment the
foo attempts for the next 60 seconds”.
In other words, if the
foo key is attempted more than 5 times in 60 seconds, the
tooManyAttempts() will return
true and nothing will be executed.
A simpler way to rate limit
The above works fine most of the time, but instead having to calling the Rate Limiter two times, with the same key, and leaving the logic between the two calls, we can do something more straightforward.
I like oneliners, and I just recently got approved a little helper called
attempt(), that should land in Laravel 8.53. What it does is the same thing, but using a callback, and with the same default of 60 seconds. Our example above can be resumed in just a conditional statement:
Since it receives a
Closure, the Rate Limiting can become portable. For example, you can save a function in the beginning and execute it later, instead of marrying the rate limiting to whatever you have between the check and the hit. As an alternative, you can always use a
callable by using
The best implementation of the
attempt() is for creating idempotent calls. For example, imagine we want to message an user, but skip the whole logic if the message is exactly the same he sent less than 2 minutes ago. That may happen if the connection is unstable, or when the user presses “Send” too many times accidentally.
No problem, we can get a hash of the message as the key and then check if it was sent previously.
The last cool thing about
attempt() is that it will return the callback result. When the callback doesn’t return anything, or
attempt() will return
For example, if we want to return a model, no problem. If the callback wasn’t executed because it reached the rate limit, then
false is always returned.