Reduce coupling by introducing a ThrottleStore service, for use by User::pingLimiter and Auth\Throttler. This would allow throttling behavior of different components to be tested easily, and would centralize the implementation of throttling algorithms.
Draft:
class ThrottleStore { /** * @param string $realm Supports at least "local" or "global" * @param string $key * @param array[] conditions A list of conditions. Each condition is an associative array * with the following keys: 'limit' is the maximum count per window, and * 'window' is the duration of a window in seconds. * @param int $amount the amount of the throttled resource to consume. When throttling * occurrences, this would be 1. When throttling volume, this would be the size the * current chunk. For peeking at the throttle without changing it, use zero. * @return array[] A list of throttle states, with each state corresponding to a condition, * and having the following keys: 'count' the current count against the limit, * 'wait' the number of seconds to with before the next attempt. The throttle * is "triggered" if at least one 'wait' is larger than 0. **/ public function throttle( string $realm, string $key, array $conditions, $amount ); }
In order to abstract different throttling algorithms, we could use the strategy pattern, allowing the ThrottleStore to use strategies like fixed window or leaky bucket.
This interface shouldn't be used directly by application logic. We'd want to wrap this in a more high level abstraction that offers methods like throttleLogin( $name, $ip ) or throttleEdit( $session ), etc.